「BalticOI 2011 Day2」Tree Mirroring 题解
本文网址: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;
}
尾声
如果你发现了问题,你可以直接回复这篇题解
如果你有更好的想法,也可以直接回复!