切了这题就 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。
#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;
}