P7520

切了这题就 AK 省选 day2 了

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

首先很显然的事情是,加边之后每个点的支配点集合只会有元素逃跑不会新加。容易发现,设 (a=mathrm{LCA}(x,y))​,如果 (operatorname{dis}(a,y)>1)​,则 (operatorname{subtree}(y))​ 的支配点集合全部改变,方案是 (1 o a o x o y)​ 绕过 (idom_y)​。简单证明一下为什么一定能找到一条这样的绕过 (idom_y)​ 的路径(虽然没啥用两千三百三十三):考虑 (1 o x)​ 的支配点路径 (1=x_0 o x_1 ocdots o x_s=x)​​,由于 (x_i o 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 o 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 ight))​,你别看这也 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]<<"!
";
   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<<"!
",*/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<<"
";
   }
   return 0;
}
珍爱生命,远离抄袭!
原文地址:https://www.cnblogs.com/ycx-akioi/p/solution-p7520.html