支配树不 sb 的题

切了这题就 AK 省选 day2 了

先建出支配树(提示的很明显了吧)。最终肯定是要一个 \(\mathrm O(nq)\) 算法对吧。\(n,m\) 同阶,复杂度都用 \(n\) 表示。

首先很显然的事情是,加边之后每个点的支配点集合只会有元素逃跑不会新加。容易发现,设 \(a=\mathrm{LCA}(x,y)\)​,如果 \(\operatorname{dis}(a,y)>1\)​,则 \(\operatorname{subtree}(y)\)​ 的支配点集合全部改变,方案是 \(1\to a\to x\to y\)​ 绕过 \(idom_y\)​。简单证明一下为什么一定能找到一条这样的绕过 \(idom_y\)​ 的路径(虽然没啥用两千三百三十三):考虑 \(1\to x\)​ 的支配点路径 \(1=x_0\to x_1\to\cdots\to x_s=x\)​​,由于 \(x_i\to x_{i+1}\)​ 的所有可能点集合为 \(\operatorname{subtree}(x_i)-\operatorname{subtree}(x_{i+1})-\{x_i\}\)​,容易发现每个点最多出现在其中一段中。那么 \(idom_y\)​ 出现的那段就是 \(x_i=a\)​,于是只需要 ban 一次 \(idom_y\)​,由于不支配,显然能做到。

但是除了 \(\operatorname{subtree}(y)\) 并不知道其它点的支配点集合改不改变……例如 \(y\) 能一步到达它的的兄弟 \(z\),这时候 \(\operatorname{subtree}(z)\) 的支配点集合也全部改变。所以我们需要一个更普遍的结论。

一个想法是,如果一个点支配点集合改变,是不是一定有它的最近支配点改变?\(\operatorname{subtree}(y)-\{y\}\)​ 全是反例……进一步发现,\(z\)​ 支配点集合改变当且仅当 \(idom_z\)​ 改变或 \(idom_z\)​​ 支配点集合改变,正确性显然。那其实就依然可以只判每个点的最近支配点是否改变​,然后 dfs 一波。

那其实就是看对每个点 \(z\)​​,去掉 \(idom_z\)​ 加上 \((x,y)\)​ 之后 \(1\)​ 是否能到 \(z\)​。由于复杂度允许平方,那这其实就非常简单。不加 \((x,y)\)​ 的时候去掉 \(idom_z\)​ 显然不存在 \(1\to z\)​,但是某些其它点被 \(1\)​ 到达;还有一些到达 \(z\)​,但不包括 \(1\)​。如果 \(x\)​ 属于前者,\(y\)​ 属于后者,那就 win 了,否则 lose。那就平方预处理就好了。

由于允许平方,支配树可以平方暴力建非常好写,方法是去掉每个点 dfs,支配所有不能到的点,每个点取在 dfs 树上 dfn 第二大的支配点作为最近支配点。以及理论上来说 dfs 占的复杂度是 \(\mathrm O(n(n+q))\)​,但其实 \(\mathrm O(nq)\)​ 都是在同一棵支配树上 dfs,对这类任务简单的 dfs 有一个小 trick:直接按 dfn 正序或倒序遍历,就不用享受递归大常数和 vector 大常数了。所以 dfs 的复杂度其实只有 \(\mathrm O\!\left(n^2\right)\)​,你别看这也 2e7 了,果断用链式前向星,不开 O2 AC。

code
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=6010;
int n,m,qu;
struct addedge{
   int sz,head[N],nxt[N],val[N];
   addedge(){sz=0;memset(head,0,sizeof(head));}
   void ae(int x,int y){
   	nxt[++sz]=head[x],head[x]=sz,val[sz]=y;
   }
}nei,rnei;
int dfn[N],nowdfn,mng[N];
bool cd(int x,int y){return dfn[x]<dfn[y];}
void dfs(int x=1){
   mng[dfn[x]=++nowdfn]=x;
//	cout<<x<<" dfn is "<<dfn[x]<<"!\n";
   for(int i=nei.head[x];i;i=nei.nxt[i]){
   	int y=nei.val[i];
   	if(!dfn[y])dfs(y);
   }
}
bool vis[N];
void dfs_ban(int x=1){
   vis[x]=true;
   for(int i=nei.head[x];i;i=nei.nxt[i]){
   	int y=nei.val[i];
   	if(!vis[y])dfs_ban(y);
   }
}
void rdfs(int x=1){
   vis[x]=true;
   for(int i=rnei.head[x];i;i=rnei.nxt[i]){
   	int y=rnei.val[i];
   	if(!vis[y])rdfs(y);
   }
}
int idom[N];
vector<int> son[N];
bool dp1[N][N],dp2[N][N];
bool chg[N];
void dfs_dom(int x=1){
   mng[dfn[x]=++nowdfn]=x;
   for(int i=0;i<son[x].size();i++)dfs_dom(son[x][i]);
}
int main(){
   cin>>n>>m>>qu;
   while(m--){
   	int x,y;
   	scanf("%d%d",&x,&y);
   	nei.ae(x,y),rnei.ae(y,x);
   }
   dfs();
   for(int i=1;i<=n;i++){
   	memset(vis,0,sizeof(vis));
   	vis[i]=true;if(!vis[1])dfs_ban();
   	for(int j=1;j<=n;j++)if(!vis[j])/*cout<<j<<" is dominated by "<<i<<"!\n",*/idom[j]=max(idom[j],i,cd);
   }
//	for(int i=1;i<=n;i++)cout<<idom[i]<<" ";puts("!");
   for(int i=2;i<=n;i++){
   	memset(vis,0,sizeof(vis));
   	vis[idom[i]]=true;if(!vis[1])dfs_ban();
   	for(int j=1;j<=n;j++)dp1[i][j]=j!=idom[i]&&vis[j];
   	memset(vis,0,sizeof(vis));
   	vis[idom[i]]=true;if(!vis[i])rdfs(i);
   	for(int j=1;j<=n;j++)dp2[i][j]=j!=idom[i]&&vis[j];
   }
   for(int i=2;i<=n;i++)son[idom[i]].pb(i);
   memset(dfn,0,sizeof(dfn)),memset(mng,0,sizeof(mng)),nowdfn=0;
   dfs_dom();
   while(qu--){
   	int x,y;
   	scanf("%d%d",&x,&y);
   	for(int i=2;i<=n;i++)chg[i]=dp1[i][x]&&dp2[i][y];
   	int ans=0;
   	for(int i=2;i<=n;i++)ans+=(chg[mng[i]]|=chg[idom[mng[i]]]);
   	cout<<ans<<"\n";
   }
   return 0;
}
珍爱生命,远离抄袭!