题解 P4672 【[BalticOI 2011 Day2]Tree Mirroring】

· · 题解

题目大意:给定一张 n 个点 m 条边的无向图,判定这张无向图是否是一张树之镜像图。若一张图可以由两棵一模一样的树共用叶节点得到,则称之为树之镜像图。

注意除根外,所有叶节点即度数为 1 的点必须全部合并。

数据范围

考虑将问题分成两步解决:

  1. 找到被合并的叶节点。
  2. 依靠叶节点对图进行判定。

我们先完成第一步。

考虑找到图上的某一个环,并去掉环上的所有边。那么剩下的图将会是这样:

我绝对不会告诉你我贴的官方题解图。

我们发现现在图中的连通块最多只包含两个环上的点。如果有包含三个以上环点的连通块可以直接判掉。

再考虑一个包含两个环点的连通块。我们分别让这两个环点 x,y 作为两棵树的根,那么对于任意一个叶节点,其到 x,y 的距离必然相同。

那么我们只需要找到任意一个环并将其所有边删去,再找到删去后一个包含两个环点的连通块,并分别从这两个环点出发 bfs,即可完成第一步。

但是我们发现,图中不一定存在环,环上的边删去后也可能不存在包含两个环点的连通块。

对于图中不存在环的情况,则必然存在一个度为 1 的点。而根据题意,显然度为 1 的点只能有 02 个。如果是 2 个点的话,则我们可以直接从这两个点出发进行 bfs。

而对于存在环但删去环边不存在一个连通块包含两个环点的情况,如果图中不存在两个度为 1 的点,则图只能是一个单独的环。而图是一个环的情况下,我们直接按照点数的奇偶性判定即可。

再来完成第二步。

首先我们需要确保叶子两边都是树。虽然看起来有很多东西要判,但其实我们可以直接判断点数与边数的关系,应满足这样一个关系式:

n-2+c=m

其中 c 为叶子的个数。

之后我们考虑从叶子往两个方向分别 bfs。容易发现此时叶子之间的对应关系已经被确定,所以上面的非叶节点之间的对应关系也必然确定,直接判断即可。

时间复杂度 O(n+m)

代码如下:

#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;
}