1 条题解

  • 0
    @ 2025-8-24 22:02:30

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar hezlik
    绒布球

    搬运于2025-08-24 22:02:30,当前版本为作者最后更新于2020-10-02 19:13:59,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


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

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

    数据范围

    • 3n,m1053\leq n,m\leq 10^5

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

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

    我们先完成第一步。

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

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

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

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

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

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

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

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

    再来完成第二步。

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

    n2+c=mn-2+c=m

    其中 cc 为叶子的个数。

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

    时间复杂度 O(n+m)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;
    }
    
    • 1

    信息

    ID
    3655
    时间
    1000ms
    内存
    250MiB
    难度
    7
    标签
    递交数
    0
    已通过
    0
    上传者