题解:P7353 [2020-2021 集训队作业] Tom & Jerry

Problem Link

思考 Tom 怎么获胜,有以下两种情况:

  1. Tom 不断限制 Jerry 的活动范围,直到困死。
  2. ~Tom 瞎走都可以赢~,有一个点能让 Tom 必胜。

对于(1),显然 Tom 需要不断走割点,由此想到圆方树。

这里有个圆方树

假设 Tom 在 \(a\),Jerry 在 \(d\),Jerry 能在 \(a\) 的子树里任意走,所以 Tom 需要让 \(a\) 能直接到达 \(b\)\(c\),否则就输了。由此看出每当 Tom 在 \(x\),Jerry 在 \(x\) 的子树 \(y\) 内时,\(x\) 必须直接到达 \(y\) 代表的点双连通分量中的任意点。此时 Tom 必胜。

对于(2),考虑能让 Tom 必胜的那个点 \(x\),此时 Jerry 可以在任何地方(否则(1)就讨论过了)。Tom 必胜条件就是对于 \(x\) 的任何子树,(1)的条件都成立。

若(1)(2)都不成立,Tom 必败。

接下来实现挺复杂的,我看其他 dalao 题解是没怎么看懂(我太蒻了),这里介绍一下我的写法。

考虑如何表示 \(x\) 能直接到达其点双连通分量的任意点。可以将这个状态放到 \(x\) 到对应方点的边上。

枚举原图上的边,如 \((a,b)\),则在圆方树上让 \(w(a,A)\)\(w(b,A)\) 各加一,这很好实现。\(a\) 能直接到 \(A\) 中任意点的充要条件是 \(w(a,A) = sz_A-1\)\(sz_A\)\(A\) 代表的点双连通分量的大小)。

必胜条件可以转化为:对于一个根为 \(x\) 的子树中,所有 \(u\) 是圆点,\(v\) 是方点,\(dep_u<dep_v\) 的边 \((u,v)\) (如下图红边)均合法。记不合法的边的数量\(f_x\),满足条件且不合法的的边称为不合法边。

容易得到转移:\(f_{y \in son_x} \rightarrow f_x\)。对于不合法边 \((u,v)\),为代码更好写,让 \(f_v \leftarrow f_v+1\)

考虑换根 dp,记 \(g_x\) 为删去 \(x\) 的子树(除了 \(x\))剩下的子树中不合法边的数量

转移为:\(g_y=g_x+f_x-f_y+\Delta\)

\(\Delta\) 的取值有:

  • \((x,y)\) 有原来是不合法边,\(\Delta=-1\)
  • \((x,y)\) 现在是不合法边,\(\Delta=1\)
  • \((x,y)\) 啥都不是,\(\Delta=0\)

最后用 lca 求出需要满足的子树 \(x\),注意特判情况(2)。

默认 \(n,m,q\) 同级,时间复杂度为 \(O(n\log n)\)

update on 2024.10.3 修改代码中的部分错误

Code:

#include<iostream>
#include<cstdlib>
#include<ctime>
#include<cassert>
#include<vector>
#include<cmath>
#include<cstring>
#include<set>
#include<queue>
#include<algorithm>
using namespace std;
const int N = 2e5 + 10, M = 2 * N;
int dfn[N], low[N], st[N], sz[N], f[N], g[N], tot;
int fa[N][21], dep[N], n, m, q, ifa[N];
vector <int> e[N];
int head[N], vi[M], ne[M], wi[M], htot;
vector < pair <int, int> > vt;
void addedge(int u, int v){
	ne[++htot] = head[u], vi[htot] = v, head[u] = htot;
}
void add(int u, int v){
	addedge(u, v);
	addedge(v, u);
}
void tarjin(int x){
	dfn[x] = low[x] = ++dfn[0], st[++st[0]] = x;
	for(int y : e[x]){
		if(!dfn[y]){
			tarjin(y);
			low[x] = min(low[x], low[y]);
			if(low[y] == dfn[x]){
				add(x, ++tot);
				sz[tot] = 2;
				while(st[st[0]] != y){
					sz[tot]++;
					add(st[st[0]--], tot);
				}
				add(st[st[0]--], tot);
			}
		}
		else
			low[x] = min(low[x], dfn[y]);
	}
}
void ad(int u, int v){
	while(u != v){
		if(dep[u] < dep[v])
			swap(u, v);
		wi[ifa[u]]++, u = fa[u][0];
	}
}
void dfs1(int x, int pfa){
	dep[x] = dep[pfa] + 1, fa[x][0] = pfa;
	for(int i = 1; i <= 20; i++)
		fa[x][i] = fa[fa[x][i - 1]][i - 1];
	for(int i = head[x]; i; i = ne[i]){
		int y = vi[i];
		if(y == pfa)
			continue;
		ifa[y] = i;
		dfs1(y, x);
	}
}
void dfs2(int x, int pfa, int op){
	f[x] = op;
	for(int i = head[x]; i; i = ne[i]){
		int y = vi[i];
		if(y == pfa)
			continue;
		dfs2(y, x, (x <= n && wi[i] != sz[y] - 1));
		f[x] += f[y];
	}
}
void dfs3(int x, int pfa){
	for(int i = head[x]; i; i = ne[i]){
		int y = vi[i];
		if(y == pfa)
			continue;
		int t = g[x] + f[x] - f[y];
		if(y <= n && wi[i] != sz[x] - 1)
			t++;
		if(x <= n && wi[i] != sz[y] - 1)
			t--;
		g[y] = t;
		dfs3(y, x);
	}
}
int lca(int x, int y){
	if(dep[x] < dep[y])
		swap(x, y);
	for(int i = 20; ~i; i--)
		if(dep[fa[x][i]] >= dep[y])
			x = fa[x][i];
	if(x == y)
		return x;
	for(int i = 20; ~i; i--)
		if(fa[x][i] != fa[y][i])
			x = fa[x][i], y = fa[y][i];
	return fa[x][0];
}
bool check(int x, int y){
	if(lca(x, y) == x){
		int z = y;
		for(int i = 20; ~i; i--)
			if(dep[fa[z][i]] > dep[x])
				z = fa[z][i];
		return f[z] == 0;
	}
	return g[x] == 0;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	cin >> n >> m >> q;
	for(int i = 1, u, v; i <= m; i++){
		cin >> u >> v;
		vt.push_back(make_pair(u, v));
		e[u].push_back(v);
		e[v].push_back(u);
	}
	tot = n;
	tarjin(1);
	dfs1(1, 0);
	for(auto i : vt)
		ad(i.first, i.second);
	dfs2(1, 0, 0);
	dfs3(1, 0);
	bool flag = 0;
	for(int i = 1; i <= n; i++)
		flag |= (f[i] + g[i] == 0);
	while(q--){
		int a, b;
		cin >> a >> b;
		if(flag){
			cout << "Yes\n";
			continue;
		}
		cout << (check(a, b)? "Yes\n" : "No\n");
	}
}
posted @ 2024-10-10 10:20  louisliang  阅读(13)  评论(0编辑  收藏  举报