题解 P4672 【[BalticOI 2011 Day2]Tree Mirroring】
题目大意:给定一张
注意除根外,所有叶节点即度数为
数据范围:
考虑将问题分成两步解决:
- 找到被合并的叶节点。
- 依靠叶节点对图进行判定。
我们先完成第一步。
考虑找到图上的某一个环,并去掉环上的所有边。那么剩下的图将会是这样:
我绝对不会告诉你我贴的官方题解图。
我们发现现在图中的连通块最多只包含两个环上的点。如果有包含三个以上环点的连通块可以直接判掉。
再考虑一个包含两个环点的连通块。我们分别让这两个环点
那么我们只需要找到任意一个环并将其所有边删去,再找到删去后一个包含两个环点的连通块,并分别从这两个环点出发 bfs,即可完成第一步。
但是我们发现,图中不一定存在环,环上的边删去后也可能不存在包含两个环点的连通块。
对于图中不存在环的情况,则必然存在一个度为
而对于存在环但删去环边不存在一个连通块包含两个环点的情况,如果图中不存在两个度为
再来完成第二步。
首先我们需要确保叶子两边都是树。虽然看起来有很多东西要判,但其实我们可以直接判断点数与边数的关系,应满足这样一个关系式:
其中
之后我们考虑从叶子往两个方向分别 bfs。容易发现此时叶子之间的对应关系已经被确定,所以上面的非叶节点之间的对应关系也必然确定,直接判断即可。
时间复杂度
代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=100000;
int n,m;
struct side{
int y,next,tag;
}e[N*2+9];
int lin[N+9],cs;
void Ins(int x,int y){e[++cs].y=y;e[cs].next=lin[x];lin[x]=cs;}
void Ins2(int x,int y){Ins(x,y);Ins(y,x);}
int deg[N+9];
void into(){
scanf("%d%d",&n,&m);
cs=1;
for (int i=1;i<=m;++i){
int x,y;
scanf("%d%d",&x,&y);
Ins2(x,y);
++deg[x];++deg[y];
}
}
int ans;
bool Check_ans_deg(){ //判断图中度数为 1 的点的数量是否合法
int cnt=0;
for (int i=1;i<=n;++i) cnt+=deg[i]==1;
return !cnt||cnt==2;
}
int vis[N+9];
void Dfs_vis(int k){
vis[k]=1;
for (int i=lin[k];i;i=e[i].next)
if (!vis[e[i].y]) Dfs_vis(e[i].y);
}
bool Check_ans_connect(){ //判断图是否连通,应该没必要
int cnt=0;
for (int i=1;i<=n;++i)
if (!vis[i]) ++cnt,Dfs_vis(i);
for (int i=1;i<=n;++i) vis[i]=0;
return cnt==1;
}
bool Check_ans_circle(){ //判断图是一个单独的环的情况
for (int i=1;i<=n;++i)
if (deg[i]^2) return 1;
ans=n&1^1;
return 0;
}
int cir[N+9],cc;
int sta[N+9],cst;
bool Dfs_cir(int k,int fa){ //找到任意一个环
int res=0;
vis[k]=1;
sta[++cst]=k;
for (int i=lin[k];i;i=e[i].next){
int y=e[i].y;
if (y==fa) continue;
if (vis[y]){
for (int i=cst;i>=1;--i){
cir[++cc]=sta[i];
if (sta[i]==y) break;
}
res=1;
}else if (Dfs_cir(y,k)) res=1;
if (res) break;
}
--cst;
return res;
}
int bel[N+9];
void Dfs_bel(int k,int b){
bel[k]=b;
for (int i=lin[k];i;i=e[i].next)
if (!e[i].tag&&!bel[e[i].y]) Dfs_bel(e[i].y,b);
}
int rot[2];
void Get_rot(){ //找到可以作为根的两个点,放入 rot[0/1] 中
//如果有两个度数为 1 的点,直接用它们作为根
int cr=0;
for (int i=1;i<=n;++i)
if (deg[i]==1) rot[cr++]=i;
if (cr) return;
//否则考虑找到一个环,然后断掉所有环边,再找包含两个环点的连通块
Dfs_cir(1,0);
for (int i=1;i<=cc;++i){
int x=cir[i],y=cir[i%cc+1];
for (int i=lin[x];i;i=e[i].next)
if (e[i].y==y) e[i].tag=e[i^1].tag=1;
}
for (int i=1;i<=cc;++i)
if (!bel[cir[i]]) Dfs_bel(cir[i],cir[i]);
for (int i=1;i<=cc;++i)
if (cir[i]^bel[cir[i]]) rot[0]=cir[i],rot[1]=bel[cir[i]];
}
int dis[2][N+9];
queue<int>q;
void Bfs_dis(int id,int st){
dis[id][st]=1;q.push(st);
for (;!q.empty();){
int t=q.front();q.pop();
for (int i=lin[t];i;i=e[i].next)
if (!dis[id][e[i].y]) dis[id][e[i].y]=dis[id][t]+1,q.push(e[i].y);
}
}
int leaf[N+9],tag[N+9];
void Get_leaf(){ //找叶子
Bfs_dis(0,rot[0]);
Bfs_dis(1,rot[1]);
for (int i=1;i<=n;++i)
if (!(leaf[i]=dis[0][i]==dis[1][i])) tag[i]=dis[0][i]>dis[1][i];
//将点分为三类
//leaf[i]=1 表示 i 是叶子
//tag[i] 表示 i 不是叶子的情况下,i 属于哪棵树
}
int Dfs_leaf_cnt(int k){
int res=1;
for (int i=lin[k];i;i=e[i].next)
if (leaf[e[i].y]&&!vis[e[i].y]) res+=Dfs_leaf_cnt(e[i].y);
return res;
}
bool Check_leaf_cnt(){ //根据点数与边数的关系判断图被叶子分开后,是不是两棵树
int cnt=0;
for (int i=1;i<=n;++i) cnt+=leaf[i];
return n-2+cnt==m;
}
int ord[2][N+9],bfs[2][N+9],co[2];
int fa[2][N+9];
void Bfs_ord(){
for (int i=1;i<=n;++i)
if (leaf[i]) q.push(i);
for (;!q.empty();){
int t=q.front(),tt=tag[t];q.pop();
if (leaf[t]) ord[0][bfs[0][t]=++co[0]]=t,ord[1][bfs[1][t]=++co[1]]=t;
else ord[tt][bfs[tt][t]=++co[tt]]=t;
for (int i=lin[t];i;i=e[i].next){
int y=e[i].y;
--deg[y];
if (bfs[tag[y]][y]) continue;
fa[tag[y]][t]=y;
if (deg[y]<=1) q.push(y);
}
}
}
bool Check_ans(){ //最后的判断
//先跑出一个bfs序,同时给两棵树上的点重新标号,再利用树上的父子关系判定
Bfs_ord();
for (int i=1;i<=co[0];++i)
if (bfs[0][fa[0][ord[0][i]]]^bfs[1][fa[1][ord[1][i]]]) return 0;
return 1;
}
void Get_ans(){
if (!Check_ans_deg()) return;
if (!Check_ans_connect()) return;
if (!Check_ans_circle()) return;
Get_rot();
Get_leaf();
if (!Check_leaf_cnt()) return;
ans=Check_ans();
}
void work(){
Get_ans();
}
void outo(){
puts(ans?"YES":"NO");
}
int main(){
into();
work();
outo();
return 0;
}