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

壹、题目描述 ¶

传送门 to Luogu

贰、题解 ¶

可以先得到原图的支配树,使用 \(\mathcal O(n^2)\) 或者 \(\mathcal O(n\log n)\) 均可。

考虑加入一条边之后,受支配集发生变化的点,反映到支配树上即,某个点 \(u\) 的某个祖先 \(anc\) 在新的支配树上不再是他的祖先了。

如果将所有这样的点全部找出来,显然不可取。

感谢 lindongli2004 的思路 给我灵感,考虑当一个点的受支配集发生变化,显然,他在原支配树上的所有后代的受支配集同样发生了变化,所以,我们只需要找在支配树上深度最浅的点,其某个祖先在新支配树上不再是其祖先,而这个深度最浅的点,发生变化的就不止是他的祖先,这个祖先更是他的父亲,不然就还有深度比他更浅的点。

也就是说,我们要找到在原支配树上,在加入 \(\lang x, y\rang\) 之后,某个点的父亲不再支配他的点,找到这样的点之后,他和他的子树都被算入答案贡献中,然后就不用再询问他的子树了。

显然,当一个点 \(u\) 的父亲 \(t\) 在加入 \(\lang x,y\rang\) 之后不再支配他,当且仅当存在一条 \(1\rightarrow x\rightarrow y\rightarrow u\) 不经过他的父亲,将这个路径拆成三个部分 这不是显然的吗,即 \(1\rightarrow x,x\rightarrow y,y\rightarrow u\),也就是要满足这样几个条件:

  • 存在一条 \(1\rightarrow x\) 的路径不经过 \(t\)
  • \(x\rightarrow y\) 不经过 \(t\)
  • 存在 \(y\rightarrow u\) 的路径不经过 \(t\)

对于第一个条件,转化一下,\(x\) 在原图上不受 \(t\) 的支配,我们只需要将所有 \(x\) 的祖先暴力打上标记就可以判断了,时间复杂度 \(\mathcal O(n)\).

对于第二个条件,判一判就好。

对于第三个条件,考虑预处理一个 \(f_{i,j}\) 表示 \(j\) 能否在不经过 \(i\) 的支配树上的父亲到达 \(i\),具体预处理方式就是枚举 \(i\),然后在原图的反图\(\tt BFS\) 一遍,途中不能扩展 \(i\) 的支配树父亲,复杂度 \(\mathcal O(n(n+m))\le \mathcal O(3n^2)\).

对于每个询问,枚举一个 \(u\),判断 \(f_{u,y}\) 并且看 \(father_u\) 是否支配 \(x\) 即可。

总复杂度 \(\mathcal O(n\log n+3n^2+nq)\).

叁、参考代码 ¶

用的 \(\mathcal O(n\log n)\) 构建支配树的方法。

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;

template<class T>inline T readin(T x){
	x=0; int f=0; char c;
	while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
	for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
	return f? -x: x;
}

const int maxn=3e3;
const int maxm=maxn*2;
const int logn=12;

struct graph{
	struct edge{
		int to, nxt;
		edge(){}
		edge(int T, int N): to(T), nxt(N){}
	}e[maxm*2+5];
	int tail[maxn+5], ecnt;
	inline void add_edge(int u, int v){
		e[ecnt]=edge(v, tail[u]); tail[u]=ecnt++;
	}
	graph(){
		ecnt=0;
		memset(tail, -1, sizeof tail);
	}
}G, rG, dag, rdag;
#define foredge(i, u, G) for(int i=G.tail[u], v; ~i? (v=G.e[i].to, 1): 0; i=G.e[i].nxt)

int n, m, q;

inline void input(){
	n=readin(1), m=readin(1), q=readin(1);
	int u, v;
	for(int i=1; i<=m; ++i){
		u=readin(1), v=readin(1);
		G.add_edge(u, v);
		rG.add_edge(v, u);
	}
}

int dfn[maxn+5], refl[maxn+5], fa[maxn+5], timer;
void dfs(int u){
	refl[dfn[u]=++timer]=u;
	foredge(i, u, G) if(!dfn[v]){
		fa[v]=u, dag.add_edge(u, v);
		dfs(v);
	}
}

int pre[maxn+5], minn[maxn+5], sdom[maxn+5];
inline void initial(){
	for(int i=1; i<=n; ++i)
		pre[i]=minn[i]=sdom[i]=i;
}
int findrt(int u){
	if(u==pre[u]) return u;
	int ret=findrt(pre[u]);
	if(dfn[sdom[minn[u]]]>dfn[sdom[minn[pre[u]]]])
		minn[u]=minn[pre[u]];
	return pre[u]=ret;
}
inline void tarjan(){
	initial();
	for(int j=n; j>1; --j){
		int u=refl[j], res=j;
		if(!u) continue;
		foredge(i, u, rG){
			if(!dfn[v]) continue;
			if(dfn[v]<dfn[u]) res=min(res, dfn[v]);
			else findrt(v), res=min(res, dfn[sdom[minn[v]]]);
		}
		sdom[u]=refl[res];
		pre[u]=fa[u];
		dag.add_edge(sdom[u], u);
	}
}

int in[maxn+5], Q[maxn+5], op, ed;

vector<int>tre[maxn+5];
int tp[maxn+5][logn+5], dep[maxn+5];

inline int getlca(int u, int v){
	if(dep[u]<dep[v]) swap(u, v);
	for(int j=logn; j>=0; --j) if(dep[tp[u][j]]>=dep[v])
		u=tp[u][j];
	if(u==v) return u;
	for(int j=logn; j>=0; --j) if(tp[u][j]!=tp[v][j])
		u=tp[u][j], v=tp[v][j];
	return tp[u][0];
}
inline void insert_tre(int u){
	int par=rdag.e[rdag.tail[u]].to;
	foredge(i, u, rdag) par=getlca(par, v);
	dep[u]=dep[par]+1, tp[u][0]=par;
	tre[par].push_back(u);
	for(int j=1; j<=logn; ++j)
		tp[u][j]=tp[tp[u][j-1]][j-1];
}
inline void topu(){
	for(int u=1; u<=n; ++u){
		foredge(i, u, dag){
			++in[v];
			rdag.add_edge(v, u);
		}
	}
	for(int i=1; i<=n; ++i) if(!in[i]){
		dag.add_edge(0, i);
		rdag.add_edge(i, 0);
		++in[i];
	}
	Q[op=ed=1]=0;
	while(op<=ed){
		int u=Q[op++];
		foredge(i, u, dag) if(!--in[v]){
			Q[++ed]=v;
			insert_tre(v);
		}
	}
}

int siz[maxn+5];
void dfstre(int u){
	siz[u]=1, refl[dfn[u]=++timer]=u;
	for(int i=0, up=tre[u].size(); i<up; ++i)
		dfstre(tre[u][i]), siz[u]+=siz[tre[u][i]];
}

int f[maxn+5][maxn+5];
// whether i can reach j without going by i's father in the dominant_tre

inline void getf(){
	// besides 1(root)
	for(int i=2; i<=n; ++i){
		Q[op=ed=1]=i, f[i][i]=1;
		while(op<=ed){
			int u=Q[op++];
			foredge(j, u, rG) if(!f[i][v] && v!=tp[i][0]){
				f[i][v]=1, Q[++ed]=v;
			}
		}
	}
}

int vis[maxn+5];
inline void climb(int u){
	while(u) vis[u]=1, u=tp[u][0];
}
inline void getquery(){
	int x, y, ans;
	while(q--){
		x=readin(1), y=readin(1), ans=0;
		memset(vis, 0, sizeof vis); climb(x);
		// pay attention, dfn[0]=1, refl[1]=0, so the real dfn is [2, n+1]
		for(int i=2; i<=n+1; ++i){
			int u=refl[i];
			if(f[u][y] && !vis[tp[u][0]])
				ans+=siz[u], i+=siz[u]-1;
		}
		printf("%d\n", ans);
	}
}

signed main(){
	input();
	dfs(1);
	tarjan();
	topu();
	// rebuild dfn[], get siz[]
	timer=0; dfstre(0);
	getf();
	getquery();
	return 0;
}

肆、用到 の \(\tt trick\)

这一段很妙啊:

考虑当一个点的受支配集发生变化,显然,他在原支配树上的所有后代的受支配集同样发生了变化,所以,我们只需要找在支配树上深度最浅的点,其某个祖先在新支配树上不再是其祖先,而这个深度最浅的点,发生变化的就不止是他的祖先,这个祖先更是他的父亲,不然就还有深度比他更浅的点。

当要统计哪些点的祖先发生变化的时候,实际上找最上面的点就行了,而这样找能将 “祖先” 转变成 “父亲”,这就好处理得多。

posted @ 2021-04-19 17:03  Arextre  阅读(278)  评论(0编辑  收藏  举报