[省选联考 2021 A 卷] 支配 题解

lindongli2004

2021-04-13 12:31:28

Solution

题意本身就已经够简洁的了,所以这里不再复述。 首先建一棵支配树(建法在最下面),那么一个点 $u$ 的受支配集 $D_v$ 即为它到根的路径上的所有点组成的集合,那么如果我们连一条边使得某一个点 $u$ 的支配集改变:首先,$u$ 的支配集不会增大,那只能是 $u$ 的某个支配树上的祖先不再支配 $u$ 了。进一步地等价于,存在一条路径,不经过 $u$ 的某个祖先可以直接到达 $u$。 那么如果只经过原图上的边,是不可能存在这样的路径的,所以一定要经过新加的有向边 $(x,y)$ 才行。那么一个对答案有贡献的点 $u$,从 $1$ 出发到它的路径都形如:$1\to x \to y \to u$ (前后两个 $\to$ 代表一条路径,中间一个 $\to$ 代表一条边),且这条路径不经过 $u$ 的某一个祖先。 祖先这个限制不好处理,那进一步地,如果可以转化为不经过 $u$ 在支配树上的父亲就好了。很幸运,我们可以完成这个转化。具体地,对于一个点 $u$,我们找到这个点最浅的满足这个条件的祖先 $v$,那么 $v$ 的支配集肯定会发生改变,$v$ 子树内所有点的支配集也都会发生改变!所以一个点的支配集改变,会影响一个子树。 所以问题又转化为,如果存在一条路径 $1\to x \to y \to u$ 不经过点 $u$ 的父亲,那么 $u$ 子树内所有点都会对答案产生贡献(支配集都会发生改变),问有多少个点会产生贡献。那么这个问题就很简单了,我们先预处理出一个点 $u$,把 $u$ 的父亲从原图的反图(把原图的有向边取反)中删去,$u$ 能到达哪些点。那么它就可能对这些点产生贡献。对于一组询问 $(x,y)$,我们只需要枚举所有可能对 $y$ 产生贡献的点,如果它的父亲不在支配树上的 $1\to x$ 这条路径上,它这棵子树就可以产生贡献,求出dfs序之后差分一下即可。 复杂度 $O(nq)$ 。 支配树的建法: 因为 $n=3000$,所以允许我们使用 $O(n^2)$ 的建法,而且在考场上感觉这种建法好写好调,性价比高。我们分别删掉 $[1...n]$ 中的每一个点,从 $1$ 开始跑 dfs,求出每个点的受支配集(如果删掉点 $u$ 后 $1$ 不能到达点 $v$ 那么 $u$ 支配 $v$)。 然后类似拓扑排序一样,首先把 $1$ 入队,然后重复进行如下操作: 1. 取出队头 $u$,对于所有受支配集中存在点 $u$ 的点,把 $u$ 从受支配集中删掉。 2. 对于所有没有入队并且受支配集中只剩下本身(支配集大小为 $1$ )的点 $v$ 入队,连边 $(u,v)$。 等队列为空,就可以得到一颗支配树了! 在放代码前,写一写在考场上的心路历程吧。 考场上读完三题,5分钟就想到了这题的大概做法,但是写完之后,对拍不断出错,于是我就陷入了出错,调试,出错,调试,出错...... 如此这样的死循环。 2.5h的时候,我绝望了,面对 $0$ 分的 T3 和压根都没来得及思考的 T1,T2 ,我头一次尝到的绝望,无助,想哭的感觉。可能每次水到渠成之时,命运总会像一块大石头,挡在我面前吧。 但我想,反正也进不了队了,豁出去了!我抖擞精神,想了一会儿 T1,T2 大概能拿 $50$,便直接回去肝 T3,终于在 $4h$ 的时候,对拍不断跳出 $AC$ 的字样,泪目。于是又花 $30$min 用手速优势敲完暴力,卡时间交题。 最后因为 T3 常数太大了,没通过民间数据,只拿到了 $75$ 分(upd:过了/se),前面两题也只有 $50$ 分。下考之后花 $10$ 分钟仔细想了想 T1,才发现是道送分题。可是命运总是这样,它不断地捉弄你,也许只有知道自己的考试策略太过激进,才能成为更稳的自己吧,也许只有经历大赛的磨炼,才能成长为更强的自己吧。 最后是稍作修(ka)改(chang)后的考场代码(要是没通过的话我会回来修锅的啊哈哈): ```cpp #include<iostream> #include<cstdio> #include<algorithm> using namespace std; const int N=3021; int n,m,tot,tot2,tot1,Did,Tid,ans; int v[N],v1[N],v2[N],stk[N*N],cnt[N],siz[N],fa[N],dfn[N],vis1[N],cf[N],vis2[N]; bool del[N],vis[N],dis[N][N],init[N],cg[N][N]; struct Edge{int to,next;}e[N<<3],e1[N<<3],e2[N<<3]; void add1(int x,int y){ e1[++tot1].to=y; e1[tot1].next=v1[x]; v1[x]=tot1; } void add2(int x,int y){ e2[++tot2].to=y; e2[tot2].next=v2[x]; v2[x]=tot2; } void add(int x,int y){ // cerr<<"add "<<x<<" -> "<<y<<endl; e[++tot].to=y; e[tot].next=v[x]; v[x]=tot; } void dfs(int x){ if(del[x])return; vis[x]=1; for(int p=v1[x];p;p=e1[p].next) if(!vis[e1[p].to])dfs(e1[p].to); } void dfs2(int x){ if(del[x])return; vis[x]=1; for(int p=v2[x];p;p=e2[p].next) if(!vis[e2[p].to])dfs2(e2[p].to); } void dfs1(int x){ siz[x]=1; dfn[x]=++Did; for(int p=v[x];p;p=e[p].next){ int kp=e[p].to; fa[kp]=x; dfs1(kp); siz[x]+=siz[kp]; } } bool Init(int x,int y){ // cerr<<"Init "<<x<<" "<<y<<" "<<dfnendl; return (dfn[y]>=dfn[x] && dfn[y]<dfn[x]+siz[x]); } void getans(int x,int y){ // if(ctt==3)cerr<<"getans "<<x<<endl; vis1[x]=vis2[x]=Tid; if(!Init(fa[x],y) && vis2[fa[x]]!=Tid)stk[++ans]=x; for(int p=v1[x];p;p=e1[p].next) if(vis1[e1[p].to]!=Tid)getans(e1[p].to,y); vis2[x]=Tid-1; } bool cmp(const int &x,const int &y){return dfn[x]<dfn[y];} int read(){ int x=0,f=1; char ch=getchar(); while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } int main() { // freopen("dominator.in","r",stdin); // freopen("dominator.out","w",stdout); n=read(); m=read(); int aq=read(); for(int x,y,i=1;i<=m;i++) x=read(),y=read(),add1(x,y),add2(y,x); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++) del[j]=vis[j]=0; del[i]=1; dfs(1); for(int j=1;j<=n;j++) if(!vis[j])dis[j][i]=1,++cnt[j]; } // for(int i=1;i<=n;i++)cout<<cnt[i]<<" ";cout<<endl; // for(int i=1;i<=n;i++,cout<<endl) // for(int j=1;j<=n;j++)cout<<dis[i][j]<<" "; int top; stk[top=1]=1; init[1]=1; for(int i=1;i<=top;i++){ // cout<<"cnt[3]="<<cnt[3]<<" i="<<i<<endl; // if(i>n)break; int w=stk[i]; // cout<<"w="<<w<<endl; // if(i==60){cout<<w<<endl;for(int j=1;j<=n;j++)cout<<cnt[j]<<" ";cout<<endl;} for(int j=1;j<=n;j++) if(dis[j][w])--cnt[j]; for(int j=1;j<=n;j++) if(cnt[j]==1 && !init[j]) init[j]=1,add(w,j),stk[++top]=j; // if(i==60){for(int j=1;j<=n;j++)cout<<cnt[j]<<" ";cout<<endl;} } fa[1]=1; dfs1(1); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++) del[j]=vis[j]=0; del[fa[i]]=1; dfs2(i); for(int j=1;j<=n;j++) cg[i][j]=vis[j]; } // cerr<<"fa:";for(int i=1;i<=n;i++)cerr<<fa[i]<<" ";cerr<<endl; // cout<<"siz:";for(int i=1;i<=n;i++)cout<<siz[i]<<" ";cout<<endl; // int ctt=0; while(aq--){ int x=read(),y=read(),x1=x; ans=0; ++Tid; while(x1!=1)vis1[x1]=Tid,x1=fa[x1]; vis1[1]=Tid; for(int i=1;i<=n+1;i++)cf[i]=0; // getans(y,x); for(int i=1;i<=n;i++) if(cg[i][y] && vis1[fa[i]]!=Tid) ++cf[dfn[i]],--cf[dfn[i]+siz[i]]; for(int i=1;i<=n;i++) cf[i]+=cf[i-1],ans+=(cf[i]!=0); // sort(stk+1,stk+ans+1,cmp); // if(ctt==5){ // cerr<<"stk:";for(int i=1;i<=ans;i++)cerr<<stk[i]<<" ";cerr<<endl; // } // int mxr=0,ct=0; // for(int i=1;i<=ans;i++){ // if(dfn[stk[i]]<=mxr)continue; // mxr=dfn[stk[i]]+siz[stk[i]]-1; ct+=siz[stk[i]]; // } // cout<<x<<" -> "<<y<<endl; printf("%d\n",ans); } return 0; } ``` 彩蛋?大样例是用脚造的,上面的代码保留了大部分我考场艰辛的调试信息,删掉调试信息也就 $2k$ 左右,所以不要被代码长度吓到啦!