P4672 [BalticOI 2011]-小清新乱搞

P4672 [BalticOI 2011] Tree Mirroring (Day2)题解

评价:小清新乱搞

第一道(应该是)独立切的黑题(尽管你谷的黑具体难度一直很随机)

建议降蓝

在这里感谢一下我的好朋友@mahaorui2012,他给我推荐了这道题,并和我一起证明了乱搞随机化做法。

他的题解(py交易

这里不提供正解做法。正解是一个线性的确定性算法,详情可见其他题解。

题意

对于一棵树 \(T\),并复制一棵与 \(T\) 同构的树 \(S\)。构造一个新的图 \(T'\),新图 \(T'\) 通过合并 \(T\)\(S\) 中相应的非根的叶节点得到。我们称这样的图为树之镜像图。

给定一个图 \(G\),你需要判断 \(G\) 是否是树之镜像图。

点数 \(n\),边数 \(m\)

$ n\le 10^5 $

样例三:

22 28
13 8
8 1
1 22
1 12
1 14
13 18
13 4
4 20
20 7
13 15
15 3
15 9
9 16
9 19
22 5
12 5
14 5
5 11
11 6
18 6
7 10
10 17
17 6
3 21
21 6
16 2
19 2
2 21
YES

做法分析

我为什么要给出样例 3 呢?好难猜啊。让我们手玩一下:

思考一下我们是怎么做的:考虑对称性,肯定每次找一个环,把它相对的两个点作为叶子,然后多试几遍就出来了。深入思考一下,这启发我们,如果有两个环的并是一个路径,那么这个路径的中点一定是叶子。

与此同时,根据手玩的过程,也不难观察到,由于对称性,以一个叶子为起始进行 bfs,如果一个点有两条不相同的最短路,那么这个点也一定是叶子。如果知道了所有叶子,那么随便一个树哈希什么的就是简单的了。并非简单,调了三百年

这个东西是一个 \(O(n)\) 的判定。

那么,如何找到一个叶子呢?如果旁边有人看了眼题解并说了一个名词,不难想到,叶子,一定是一个极长的中间点度数均为 \(2\) 的链的中点。如图中的 $6 \rightarrow 17 \rightarrow 10 \rightarrow 7 \rightarrow 20 \rightarrow 4 \rightarrow 13 $,其中点为 \(7\)

单这一点只有必要性,没有充分性啊。比如说图中的 \(6 \rightarrow 11 \rightarrow 5\),这条链的中点 \(11\) 就不是一个叶子。

但是,注意到,一个极长链至少合并了一个叶子,也就是 \(O(leaf)\) 条的,而包含叶子的极长链显然也是 $ O(leaf) $ 的。树有两颗,所以包含叶子的极长链数量应该在 $ \frac{1}{3} $ 量级。

这非常启示我们乱搞随机化。每次找一条链并将其中点设为叶子,然后跑一遍判定,然后随机我们的链跑个几百次(实际上 \(200\) 次就会超时了……),并声称大概率找到,然后就做完啦!

记得特判偶环。我这里跑个 \(60\) 次就能 AC 了。

代码

理论上这个算法能处理链的情况,但是由于我实现比较唐,所以需要特判……
修复:降低枚举次数即可

类似的还有是否有超过两个点度数等于 \(1\) 的情况也是类似,不加特判会 TLE awa
修复:降低枚举次数即可

补充:理论上应该找链而非找点,否则复杂度错误。实现上,也就是找到一条链后直接把这条链上所有点给删掉(即 canguessvis),但是汤比作者忘了……不过也能过。
已修复。顺便解释一下,一条链如果度数为 \(2\) 的节点数量是 \(0\),那么这条链中一定没有叶子;反之一个度数为 \(2\) 节点一定在且仅在一条链中,所以这么做不会误删没到过的链。

void ANS(bool x){if(x==0) puts("NO");else puts("YES");exit(0);}

vector <int> e[N];
vector <int> canguess;
bool vis[N];

vector <int> lik;
void find_link(int x){
	lik.clear();
	int last=0,next=0;
	while(e[x].size()==2){
		for(int i:e[x]) if(i!=last) next=i;
		last=x,x=next;
	}
	lik.push_back(x);vis[x]=1;
	swap(last,x);
	lik.push_back(x);vis[x]=1;
	while(e[x].size()==2){
		for(int i:e[x]) if(i!=last) next=i;
		last=x,x=next;lik.push_back(x);vis[x]=1;
	}
}

vector <int> leaf;
queue <int> q;
int dis[N],num[N];
LL has[N];
int du[N];
bool check(int x)
{
	while(!q.empty()) q.pop();//bfs 找叶子
	memset(dis,0x3f,sizeof(dis));
	leaf.clear();
	q.push(x);dis[x]=0;num[x]=2;
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int v:e[u]){
			if(dis[v]>dis[u]+1){
				dis[v]=dis[u]+1;num[v]=1;
				q.push(v);
			}
			else if(dis[v]==dis[u]+1) num[v]++;
		}
	}
	For(i,1,n) if(dis[i]==INF) ANS(0),exit(0);//判掉不连通
	For(i,1,n)
		if(num[i]==2) leaf.push_back(i);
		else if(num[i]>=3) return 0;
	while(!q.empty()) q.pop();memset(du,0,sizeof(du));
	const LL mod=1000000000000037;//树哈希部分
	for(int i:leaf) has[i]=rd(1,mod-1),du[i]=-1,q.push(i);
	while(!q.empty()){
		int u=q.front();q.pop();
		for(int v:e[u]){
			if(du[v]==-1) continue;
			du[v]++;has[v]=(__int128)(has[v]==0?1:has[v])*has[u]%mod;//孩子们一定要赋初始值 QAQ
			if(du[v]>=e[v].size()-1) has[v]=(has[v]+1)%mod,du[v]=-1,q.push(v);
		}
	}
	For(i,1,n) if(has[i]==0) return 0;
	for(int i:leaf) has[i]=0;
	sort(has+1,has+1+n);
	For(i,1,n){
		if(has[i]==0) continue;
		if(has[i]!=has[i+1]) return 0;//判断点权是否对称(成对)
		else i+=1;
	}
	return 1;
}

int Test=1;
void mian()
{
	read(n),read(m);
	int real_node=m/2+1;
	int leaves=real_node*2-n;
	For(i,1,m){int u,v;read(u),read(v);e[u].push_back(v),e[v].push_back(u);}
	int d1=0,d2=0,d3=0;
	For(i,1,n)
		if(e[i].size()==1) d1++;
		else if(e[i].size()==2) d2++;
		else d3++;
	//if(d1==2&&d3==0) ANS(n&1);//特判链,但这个算法理论上能处理链的。
	if(d1==0&&d3==0) ANS(!(n&1));//特判偶环。
	//if(d1>2) ANS(0);//如果有两个度数为 1 的点就完蛋啦~不过理论上算法能处理。
	For(i,1,50){
		For(i,1,n) if(!vis[i]&&e[i].size()==2) canguess.push_back(i);
		if(canguess.empty()) ANS(0);
		int x=rd(0,canguess.size()-1);x=canguess[x];
		find_link(x);//找链
		if(!(lik.size()&1)) continue;//必须是奇数链,否则不存在叶节点
		int leaf=lik[lik.size()>>1];
		if(check(leaf)) ANS(1);
	}
	ANS(0);
}

AC Code(old)

AC Code(new)

参考资料

本文没有也不需要其他参考资料(我终于能写一次这句话啦)

update on 26/7/21 修复代码中错误/可优化的部分。更新了参考资料。

posted @ 2026-07-17 15:36  FarrisL  阅读(7)  评论(0)    收藏  举报