9.16 支配对
支配对问题一般是给出一些限制,求限制内所有点对的最优值。对于两个点对 \((u_1,v_1)\) 与 \((u_2,v_2)\),若点对 \((u_1,v_1)\) 的限制比 \((u_2,v_2)\) 更松(或者说在统计点对二时一定能统计到点对一),且其值比第二个点对的值更优,那么在任何情况下 \((u_2,v_2)\) 的贡献一定会被 \((u_1,v_1)\) 的贡献覆盖。因此,在统计答案时只需统计 不被任何点对支配 的点对即可。我们称这样的点对为支配对,其数量级一般是 \(O(n)\) 或 \(O(n\log n)\) 量级
当一个点对限制比你宽松、权值比你更优,你就被淘汰了
[JSOI2009] 面试的考验
不妨设 \(a_i<a_j,i<j\) ,该点对 \((i,j)\) 的值为 \(a_j-a_i\) 。考虑令 \(a_i\) 右侧第一个大于它的位置 \(j\) 与其组成点对 \((i,j)\) 与另一点对 \((i,k)\) 满足 \(a_i<a_k,j<k\),若 \((i,k)\) 不被 \((i,j)\) 与 \((j,k)\) 支配,那么其需要满足
即 \(a_k\in(a_i,\frac{a_i+a_j}{2})\)。将上述式子变形后有 \(a_k-a_i<\frac{a_j-a_i}{2}\),发现每找到一新的点对,其差值必定减半,所以支配对的总数是 \(O(n\log n)\) 级别的。
在找到所有支配对后,每次查询 \([L,R]\) 我们只需要统计 \(L\leqslant l<r\leqslant R\) 的所有点对的权值最小值,这是一个经典二维数点问题,倒序扫描加树状数组维护前缀 \(\min\) 即可。总复杂度 \(O(n\log^2 n)\)
代码
#include <bits/stdc++.h>
#define pii pair<int,int>//(r,val)
#define fst first
#define scd second
using namespace std;
const int N=1e5+5;
const int inf=1e9;
int n,Q,a[N];
struct node { int x,id; };
vector <node> q[N];
vector <pii> ad[N];
int ans[N];
struct BIT
{
int tr[N];
BIT() { memset(tr,0x3f,sizeof tr); }
int lowbit(int x) { return x&-x; }
void add(int x,int k) { while (x<=n) { tr[x]=min(tr[x],k); x+=lowbit(x); } }
int sum(int x)
{
int res=inf;
while (x) { res=min(res,tr[x]); x-=lowbit(x); }
return res;
}
}bt;
struct Segment_Tree
{
struct node { int ls,rs,mn; }tr[N<<6]; int cnt;
int MIN(int x,int k) { if (x>k) swap(x,k); return (x==0?k:x); }
void push_up(int id) { tr[id].mn=MIN(tr[tr[id].ls].mn,tr[tr[id].rs].mn); }
int query(int id,int l,int r,int ql,int qr)
{
if (!id||ql>qr) return 0;
if (l>=ql&&r<=qr) return tr[id].mn;
int mid=(l+r)>>1,res=0;
if (mid+1<=qr) res=query(tr[id].rs,mid+1,r,ql,qr);
if (mid>=ql) res=MIN(res,query(tr[id].ls,l,mid,ql,qr)); return res;
}
int update(int id,int l,int r,int pos,int k)
{
int p=++cnt; tr[p]=tr[id];
assert(cnt<(N<<6));
if (l==r) { tr[p].mn=MIN(tr[p].mn,k); return p; }
int mid=(l+r)>>1;
if (pos<=mid) tr[p].ls=update(tr[id].ls,l,mid,pos,k);
else tr[p].rs=update(tr[id].rs,mid+1,r,pos,k); push_up(p); return p;
}
void clr() { for (int i=1;i<=cnt;i++) tr[i]={0,0,0}; cnt=0; }
}Tr;
int rt[N];
void solve()
{
Tr.clr();
int i=n;
for (int i=n;i>=1;i--)
{
rt[i]=Tr.update(rt[i+1],1,inf,a[i],i);
int x=Tr.query(rt[i],1,inf,a[i]+1,inf);
while (x)
{
assert(x>=1&&x<=n);
ad[i].push_back({x,a[x]-a[i]});
x=Tr.query(rt[x+1],1,inf,a[i]+1,(a[i]+a[x]+1)/2-1);
}
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>Q; int l,r;
for (int i=1;i<=n;i++) cin>>a[i];
for (int i=1;i<=Q;i++)
{
cin>>l>>r;
q[l].push_back({r,i});
}
solve(); for (int i=1;i<=n;i++) a[i]=inf+1-a[i]; solve();
for (int i=1;i<=Q;i++) ans[i]=inf;
for (int i=n;i>=1;i--)
{
int _size=ad[i].size();
for (int j=0;j<_size;j++) bt.add(ad[i][j].fst,ad[i][j].scd);
_size=q[i].size();
for (int j=0;j<_size;j++) ans[q[i][j].id]=min(ans[q[i][j].id],bt.sum(q[i][j].x));
}
for (int i=1;i<=Q;i++) cout<<ans[i]<<"\n"; return 0;
}
[Ynoi2004] rpmtdq&[ICPC 2022 Jinan R] Tree Distance
显然不能考虑所有路径。我们令 \(u<v\),若 \((u,v)\) 是支配对,那么 \(\forall i\in (u,v),dis(i,v)>dis(u,v)\)。这样的形式并不好看。考虑在点分治过程中处理点对,记 \(x\) 为当前分治中心,\(d_u\) 为 \(dis(u,x)\),钦定 \(d_u\leqslant d_v,u<v\),那么上述条件等价于 \(\forall i\in (u,v),d_u<d_i\),即 \(u\) 是 \(v\) 前第一个小于等于它的数。那么考虑将此次分支过程的所有点按序插入,用单调栈前后扫两遍即可。若 \(u,v\) 来自同一子树,那么其统计的距离一定更劣,不会影响答案。支配对总数为 \(O(n\log n)\)。
求出支配对后查询仍是倒序扫描线维护前缀最小值。总复杂度 \(O(n\log ^2 n)\)
代码
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define fst first
#define scd second
using namespace std;
const int N=2e5+5;
const int Q=1e6+5;
const int inf=1e18;
int n,q;
struct node { int nxt,val; };
vector <node> tr[N];
vector <pii> ad[N],qy[N];
int ans[Q];
struct BIT
{
int tr[N];
BIT() { memset(tr,0x3f,sizeof tr); }
int lowbit(int x) { return x&-x; }
void ins(int x,int y) { while (x<=n) { tr[x]=min(tr[x],y); x+=lowbit(x); } }
int qry(int x)
{
int res=inf;
while (x) { res=min(res,tr[x]); x-=lowbit(x); }
return res;
}
}bt;
int rt,siz[N],mx[N],vis[N],dis[N],cnt;
pii tmp[N];
int st[N],tp;
void get_rt(int x,int _fa,int tol)
{
siz[x]=1,mx[x]=0;
int _size=tr[x].size(),v;
for (int i=0;i<_size;i++)
{
if ((v=tr[x][i].nxt)==_fa||vis[v]) continue;
get_rt(v,x,tol); siz[x]+=siz[v];
mx[x]=max(mx[x],siz[v]);
}
mx[x]=max(mx[x],tol-siz[x]);
if (!rt||mx[x]<mx[rt]) rt=x;
}
void dfs(int x,int _fa)
{
tmp[++cnt]={x,dis[x]};
int _size=tr[x].size(),v;
for (int i=0;i<_size;i++)
{
if ((v=tr[x][i].nxt)==_fa||vis[v]) continue;
dis[v]=dis[x]+tr[x][i].val; dfs(v,x);
}
}
void calc()
{
sort(tmp+1,tmp+1+cnt);
tp=0;
for (int i=1;i<=cnt;i++)
{
while (tp&&tmp[i].scd<=tmp[st[tp]].scd) ad[tmp[st[tp]].fst].push_back({tmp[i].fst,tmp[i].scd+tmp[st[tp]].scd}),tp--;
st[++tp]=i;
}
tp=0;
for (int i=cnt;i>=1;i--)
{
while (tp&&tmp[i].scd<=tmp[st[tp]].scd) ad[tmp[i].fst].push_back({tmp[st[tp]].fst,tmp[i].scd+tmp[st[tp]].scd}),tp--;
st[++tp]=i;
}
}
void solve(int x,int tol)
{
vis[x]=1; cnt=0; dis[x]=0;
tmp[++cnt]={x,dis[x]};
int _size=tr[x].size(),v;
for (int i=0;i<_size;i++)
{
if (vis[v=tr[x][i].nxt]) continue;
dis[v]=tr[x][i].val; dfs(v,x);
}
calc();
for (int i=0;i<_size;i++)
{
if (vis[v=tr[x][i].nxt]) continue;
int sz=(siz[v]>siz[x]?tol-siz[x]:siz[v]);
rt=0; get_rt(v,0,sz); solve(rt,sz);
}
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n; int u,v,w;
for (int i=1;i<n;i++)
{
cin>>u>>v>>w;
tr[u].push_back({v,w});
tr[v].push_back({u,w});
}
cin>>q;
for (int i=1;i<=q;i++)
{
cin>>u>>v;
if (u==v) ans[i]=-1;
else qy[u].push_back({v,i});
}
get_rt(1,0,n); solve(rt,n);
for (int i=n;i>=1;i--)
{
int _size=ad[i].size();
for (int j=0;j<_size;j++) bt.ins(ad[i][j].fst,ad[i][j].scd);
_size=qy[i].size();
for (int j=0;j<_size;j++) ans[qy[i][j].scd]=bt.qry(qy[i][j].fst);
}
for (int i=1;i<=q;i++) cout<<ans[i]<<"\n"; return 0;
}
[Ynoi2006] rldcot
朴素想法为统计所有点对 \((i,j)\) 的 LCA 深度,但这显然是不可取的。考虑点对 \(u,v\) 是支配对当且仅当 \(\forall i,j\in [l,r],lca(i,j)\neq lca(u,v)\)。我们令 \(x=lca(u,v)\),\((u,v)\) 符合上述式子当且仅当在 \(x\) 子树中, \(u\) 是 除了 \(v\) 所在子树 的子树中 \(v\) 的前驱(即那若干子树中最大的小于 \(v\) 的点),反之即是 \(v\) 的后继。
考虑在 dsu on tree 过程中求出所有支配对,用 set 维护前面子树中的节点编号,每次在 set 中查询当前子树内所有节点的前驱、后继。鉴于 dsu on tree 的复杂度,支配对的总数也为 \(O(n\log n)\) 级别。
当求出所有支配对后,将 \(dep_{lca}\) 看作其颜色,每次查询就变成了区间数颜色问题。考虑所有点对按照右端点升序插入,每个颜色只在其出现过的最靠右的左端点处贡献,用树状数组查询在大于等于 \(l\) 位置出现过的颜色数即可。复杂度 \(O(n\log^2 n)\)
代码
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define fst first
#define scd second
#define itt set <int>::iterator
using namespace std;
const int N=1e5+5;
const int M=5e5+5;
int n,m;
struct Edge { int nxt,val; };
vector <Edge> tr[N];
vector <pii> q[N];
vector <pii> ad[N];
unordered_map <int,int> mp;
set <int> st;
int ans[M];
struct BIT
{
int tr[N];
int lowbit(int x) { return x&-x; }
void add(int x,int y) { while (x<=n) { tr[x]+=y; x+=lowbit(x); } }
int sum(int x)
{
int res=0;
while (x) { res+=tr[x]; x-=lowbit(x); }
return res;
}
}bt;
int read()
{
int f=1,r=0; char c=getchar();
while (c<'0'||c>'9') f^=(c=='-'),c=getchar();
while (c>='0'&&c<='9') r=(r<<3)+(r<<1)+(c^48),c=getchar();
return (f?r:-r);
}
int siz[N],son[N];
int dis[N],dfn[N],tme,idx[N];
void dfs1(int x,int _fa)
{
siz[x]=1;
int _size=tr[x].size(),v;
for (int i=0;i<_size;i++)
{
if ((v=tr[x][i].nxt)==_fa) continue;
dis[v]=dis[x]+tr[x][i].val;
dfs1(v,x); siz[x]+=siz[v];
if (siz[v]>siz[son[x]]) son[x]=v;
}
}
void dfs2(int x,int _fa)
{
idx[dfn[x]=++tme]=x;
if (son[x]) dfs2(son[x],x);
int _size=tr[x].size(),v;
for (int i=0;i<_size;i++) if ((v=tr[x][i].nxt)!=_fa&&v!=son[x]) dfs2(v,x);
}
void calc(int x,int val)
{
itt it=st.lower_bound(x);
if (it!=st.end()) ad[x].push_back({*it,val});
if (it==st.begin()) return ;
it--; ad[*it].push_back({x,val});
}
void solve(int x,int _fa,int f)
{
int _size=tr[x].size(),v;
for (int i=0;i<_size;i++) if ((v=tr[x][i].nxt)!=_fa&&v!=son[x]) solve(v,x,0);
if (son[x]) solve(son[x],x,1);
st.insert(x); calc(x,dis[x]);
for (int i=0;i<_size;i++)
{
if ((v=tr[x][i].nxt)==_fa||v==son[x]) continue;
for (int j=dfn[v];j<dfn[v]+siz[v];j++) calc(idx[j],dis[x]);
for (int j=dfn[v];j<dfn[v]+siz[v];j++) st.insert(idx[j]);
}
if (f) return ;
for (int i=dfn[x];i<dfn[x]+siz[x];i++) st.erase(idx[i]);
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
n=read(),m=read(); int u,v,w;
for (int i=1;i<n;i++)
{
u=read(),v=read(),w=read();
tr[u].push_back({v,w});
tr[v].push_back({u,w});
}
for (int i=1;i<=m;i++) { u=read(),v=read(); q[u].push_back({v,i}); }
dfs1(1,0),dfs2(1,1); solve(1,0,1);
for (int i=n;i>=1;i--)
{
int _size=ad[i].size();
for (int j=0;j<_size;j++)
{
int y=ad[i][j].fst,d=ad[i][j].scd;
if (!mp[d]) { mp[d]=y; bt.add(y,1); }
if (mp[d]>y) { bt.add(mp[d],-1),bt.add(y,1); mp[d]=y; }
}
_size=q[i].size();
for (int j=0;j<_size;j++) ans[q[i][j].scd]=bt.sum(q[i][j].fst);
}
for (int i=1;i<=m;i++) cout<<ans[i]<<"\n"; return 0;
}
[Ynoi2003] 铃原露露
666 看错题意了
大家好,我又看错题意了
我们称一个区间 \([l,r]\) 是合法的当且仅当 \(\forall u,v\in[l,r],lca(u,v)\in[l,r]\)。题意即为每次询问 \([L,R]\) 的多少个子区间是合法的。考虑到区间合法的限制比较强,我们先求点对 \((u,v,lca(u,v))\) 会使得哪些区间不合法。为表述方便,我们令 \(x=lca(u,v)\)
-
若 \(u\leqslant x\leqslant v\),它不会对区间的合法性造成影响
-
若 \(x\leqslant u<v\),那么当 \(L\in(x,u],R\geqslant v\) 时区间不合法(覆盖了点 \(u,v\) 但未覆盖 \(x\))
-
若 \(u<v\leqslant x\),那么当 \(R\in[v,x),L\in[1,u]\) 时区间不合法。
我们称会使得部分区间不合法的点对 \((u,v)\) 是邪恶的
考虑在二维平面上表示 \((l,r)\) 点(这里指区间,而非树上点对),那么以上所述的两种邪恶点对会使得平面上一个矩阵的点都不合法。查询时矩阵查询未被覆盖过的点的数量即可。
但我们肯定不能求出所有的邪恶点对。注意到部分邪恶点对的贡献会被其他邪恶点对覆盖,所以我们只需统计支配对的贡献。仍是在 dsu on tree 的过程中统计每个点的前驱后继,仍是 \(O(n\log n)\) 级别个支配对。
之后就是一个矩阵查询零的个数。考虑扫描线,此时“矩阵”信息就变为了历史信息,我们可以动用吉司机线段树的思想维护当前区间 \(\min\),区间 \(\min\) 的个数,历史版本 \(0\) 的个数,与区间加 \(taga\)、历史最小 \(tagb\)、达到历史最小 \(tagb\) 的次数 \(tagc\)。具体地,扫描线到当前维时我们相当于需要在所有修改操作进行完之后给所有零的位置的出现次数 +1,在线段树上即为给最小值为 \(0\) 的区间加上区间最小值的数量;在 push_tag 的过程中,区间需根据 \(tag\) 判断该区间是否达到过零,与达到过几次零,再进行对应次数的权值修改即可。复杂度 \(O(n\log^2 n)\)
代码
#include <bits/stdc++.h>
#define int long long
#define itt set <int>::iterator
using namespace std;
const int N=2e5+5;
const int inf=1e18;
int n,m,a[N];
vector <int> tr[N];
set <int> st;
struct node { int l,r,id; };
vector <node> ad[N],qy[N];
int ans[N];
struct Segment_Tree
{
struct node{
int l,r;
int mn,cnt,sum;
int taga,tagb,tagc;
}tr[N<<2];
void _update(int id,int k1,int k2,int ct)//普通区间加 tag,历史最小值区间加 tag,历史最小值出现次数
{
if (tr[id].mn+k2==0) tr[id].sum+=tr[id].cnt*ct;
if (tr[id].taga+k2==tr[id].tagb) tr[id].tagc+=ct;
else if (tr[id].taga+k2<tr[id].tagb) tr[id].tagb=tr[id].taga+k2,tr[id].tagc=ct;
tr[id].mn+=k1; tr[id].taga+=k1;
}
void push_down(int id)
{
_update(id<<1,tr[id].taga,tr[id].tagb,tr[id].tagc);
_update(id<<1|1,tr[id].taga,tr[id].tagb,tr[id].tagc);
tr[id].taga=tr[id].tagc=0; tr[id].tagb=inf;
}
void push_up(int id)
{
tr[id].sum=tr[id<<1].sum+tr[id<<1|1].sum;
if (tr[id<<1].mn==tr[id<<1|1].mn) { tr[id].mn=tr[id<<1].mn; tr[id].cnt=tr[id<<1].cnt+tr[id<<1|1].cnt; }
else if (tr[id<<1].mn<tr[id<<1|1].mn) { tr[id].mn=tr[id<<1].mn; tr[id].cnt=tr[id<<1].cnt; }
else { tr[id].mn=tr[id<<1|1].mn; tr[id].cnt=tr[id<<1|1].cnt; }
}
void build(int id,int l,int r)
{
tr[id].l=l,tr[id].r=r,tr[id].tagb=inf;
if (l==r) { tr[id].cnt=1; return ; }
int mid=(l+r)>>1;
build(id<<1,l,mid),build(id<<1|1,mid+1,r); push_up(id);
}
void update(int id,int l,int r,int k)
{
if (tr[id].l>=l&&tr[id].r<=r) { _update(id,k,k,(k>=0)); return ; }
int mid=(tr[id].l+tr[id].r)>>1; push_down(id);
if (mid>=l) update(id<<1,l,r,k);
if (mid+1<=r) update(id<<1|1,l,r,k); push_up(id);
}
int query(int id,int l,int r)
{
if (tr[id].l>=l&&tr[id].r<=r) return tr[id].sum;
int mid=(tr[id].l+tr[id].r)>>1,res=0; push_down(id);
if (mid>=l) res=query(id<<1,l,r);
if (mid+1<=r) res+=query(id<<1|1,l,r); return res;
}
}Tr;
int siz[N],son[N],dfn[N],tme,idx[N];
void dfs(int x)
{
siz[x]=1; idx[dfn[x]=++tme]=x;
int _size=tr[x].size(),v;
for (int i=0;i<_size;i++)
{
dfs(v=tr[x][i]); siz[x]+=siz[v];
if (siz[v]>siz[son[x]]) son[x]=v;
}
}
void calc(int v,int x)
{
if (v>x)
{
itt it=st.upper_bound(v);
if (it!=st.end()) ad[*it].push_back({x+1,v,1});
if (it==st.begin()) return ;
if ((*(--it))>x) ad[v].push_back({x+1,*it,1});
}
else
{
itt it=st.lower_bound(v);
if (it!=st.end()&&(*it)<x) { ad[*it].push_back({1,v,1}); ad[x].push_back({1,v,-1}); }
if (it==st.begin()) return ;
it--; ad[v].push_back({1,(*it),1}); ad[x].push_back({1,(*it),-1});
}
}
void solve(int x,int f)
{
int _size=tr[x].size(),v;
for (int i=0;i<_size;i++) if ((v=tr[x][i])!=son[x]) solve(v,0);
if (son[x]) solve(son[x],1);
for (int i=0;i<_size;i++)
{
if ((v=tr[x][i])==son[x]) continue;
for (int j=dfn[v];j<dfn[v]+siz[v];j++) calc(idx[j],x);
for (int j=dfn[v];j<dfn[v]+siz[v];j++) st.insert(idx[j]);
}
st.insert(x);
if (f) return ;
for (int i=dfn[x];i<dfn[x]+siz[x];i++) st.erase(idx[i]);
}
signed main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m; int f,l,r;
for (int i=1;i<=n;i++) cin>>a[i];
for (int i=2;i<=n;i++) { cin>>f; tr[a[f]].push_back(a[i]); }
for (int i=1;i<=m;i++) { cin>>l>>r; qy[r].push_back({l,n,i}); }
dfs(a[1]); solve(a[1],1); Tr.build(1,1,n);
for (int i=1;i<=n;i++)
{
int _size=ad[i].size();
for (int j=0;j<_size;j++) Tr.update(1,ad[i][j].l,ad[i][j].r,ad[i][j].id);
Tr.update(1,1,i,0);
_size=qy[i].size();
for (int j=0;j<_size;j++) ans[qy[i][j].id]=Tr.query(1,qy[i][j].l,qy[i][j].r);
}
for (int i=1;i<=m;i++) cout<<ans[i]<<"\n"; return 0;
}

浙公网安备 33010602011771号