「BalticOI 2011 Day2」Tree Mirroring 题解

发布时间 2023-07-09 19:06:06作者: zsc985246

本文网址:https://www.cnblogs.com/zsc985246/p/17539182.html ,转载请注明出处。

题目大意

现在有一棵树 \(T\),复制一个完全相同的 \(T'\),并将这两棵树的叶子节点全部对应合并在一起,形成一个图,我们称这种图为对称图

给定一个图,判断它是否为对称图。

\(3 \le n,m \le 10^5\)

思路

我们称原树中的叶子节点组成对称轴

思考如果知道两棵树的根节点,能否找到对称轴上的所有点,进而判断是否为对称图。

发现一个点到两个根节点的距离相等当且仅当它在对称轴上

那么我们可以对两个根节点各做一次 bfs 计算距离来求出这些点。

在 bfs 时记录每个点在两个根节点的 bfs 树上的父亲,然后我们就可以从对称轴向两边的父亲拓展,每次将两个点匹配,如果发现重复匹配则不合法。

这样我们就可以通过两个根节点来进行 \(O(n)\) 判断合法性。

直接枚举根节点可以做到 \(O(n^3)\) 的复杂度,可以拿到 \(30\) 分。

判定过程无法优化,考虑优化枚举过程

发现如果有解,只要是对称的两个点都可以是根节点。

又因为对称轴上节点的度数一定为 \(2\),所以可以直接枚举这个度数为 \(2\) 的点,然后以相邻的两个点作为根节点判断。

这样就把复杂度优化到了 \(O(n^2)\),拿到 \(60\) 分。

如果想要继续优化,必须要有一种能保证找到一对对称点的 \(O(n)\) 方法。

考虑图中的环

首先把环长为 \(n\) 和没有环的情况判掉。

发现如果合法,那么任意一个环外点到环上一定有且仅有两条路径。

再进一步地,我们发现这两条路径的终点是一对对称点

那么就可以做到 \(O(n)\) 了。

需要注意的是,给定的是任意图,所以有可能两边是对称的图而不是树。

对于此,我们只需要记录对称轴上的点数 \(cnt\),然后判断 \(n+cnt-2\) 是否与 \(m\) 相等。

代码实现

#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;

ll n,m;
vector<ll>e[N];
ll in[N];

ll fa[2][N],dis[2][N];//记录两个根节点的bfs树上的父亲和距离
void chk(ll opt,ll s){//处理fa[opt]和dis[opt]
	For(i,1,n)fa[opt][i]=dis[opt][i]=-1;
	queue<ll>q;
	q.push(s);
	fa[opt][s]=dis[opt][s]=0;
	while(q.size()){
		ll x=q.front();
		q.pop();
		for(ll y:e[x]){
			if(~fa[opt][y])continue;
			q.push(y);
			dis[opt][y]=dis[opt][x]+1;
			fa[opt][y]=x;
		}
	}
}
ll match[N];//match[i]记录与i匹配的点
bool calc(ll x,ll y){//拓展匹配
	while(x&&y&&!match[x]&&!match[y]){
		match[x]=y,match[y]=x;//匹配
		x=fa[0][x],y=fa[1][y];//跳父亲
	}
	return match[x]==y&&match[y]==x;//是否重复匹配
}
bool check(ll s1,ll s2){//判断图是否合法,s1和s2为两个根节点
	chk(0,s1),chk(1,s2);
	ll ans=1,cnt=0;
	For(i,1,n){
		if(dis[0][i]==dis[1][i]){//距离相等
			ans&=calc(fa[0][i],fa[1][i]);//拓展匹配
			++cnt;//对称轴上点的个数
		}
	}
	return ans&&n+cnt-2==m;//判断对称的是树
}

ll top,s[N];//记录dfs经过的节点,方便找环
ll vis[N];//标记经过的点,特别地,环上的点vis为2
bool find(ll x,ll fat){//找环
	s[++top]=x;
	vis[x]=1;
	for(ll y:e[x]){
		if(y==fat)continue;
		if(vis[y]){
			do{vis[s[top]]=2;}while(s[top--]!=y);//标记环上的点
			return true;
		}
		if(find(y,x))return true;//找到就退出
	}
	return false;
}
bool bfs(ll sx){//找到sx到环的路径终点,sx是一个环外点
	vector<ll>t;//记录路径终点
	queue<ll>q;
	q.push(sx);
	vis[sx]=1;
	while(q.size()){
		ll x=q.front();
		q.pop();
		for(ll y:e[x]){
			if(vis[y]){
				if(vis[y]==2)t.pb(y);
				continue;
			}
			vis[y]=1;
			q.push(y);
		}
	}
	return t.size()==2&&check(t[0],t[1]);//合法时只有两条路径
}

int main(){
	
	scanf("%lld%lld",&n,&m);
	For(i,1,m){
		ll x,y;
		scanf("%lld%lld",&x,&y);
		e[x].pb(y),e[y].pb(x);
		++in[x],++in[y];
	}
	//有度数为1的点
	vector<ll>t;
	For(i,1,n)if(in[i]==1)t.pb(i);
	if(t.size()==2){//将这两个点作为根节点判断
		if(check(t[0],t[1]))printf("YES");
		else printf("NO");
		return 0;
	}
	if(t.size()){//合法时只可能有2个或0个度数为1的点
		printf("NO");
		return 0;
	}
	
	find(1,0);//找环
	For(i,1,n)if(vis[i]!=2)vis[i]=0;//清空
	For(i,1,n){
		if(!vis[i]){
			if(bfs(i))printf("YES");
			else printf("NO");
			return 0;
		}
	}
	//环长为n
	if(n&1)printf("NO");
	else printf("YES");
	
	return 0;
}

尾声

如果你发现了问题,你可以直接回复这篇题解

如果你有更好的想法,也可以直接回复!