• CSP2019 树上的树 口胡


    拖了一年, 今天上午终于把这道题做出来了。


    基本思路

    题目要求字典序最小, 而字典序是有着天然的贪心性质的, 可以比较自然地想到要应用贪心算法, 进一步地, 要思考 “某个数字最终停留在某个点上” 会对全局的删边顺序产生哪些限制。
    比如有这样一条路径: (s--^a--o--^b--o--^c--o--^d--t), 如果初始时点 s 上的数字最终停留在点 t, 那么一定满足:

    1. 边 a 是与点 s 相邻的边中第一个被删去的边
    2. 对于路径上的点 o(们), 在其相邻的边中, b 一定紧接着 a 被删去, c 一定紧接着 b 被删去
    3. 边 d 是与点 t 相邻的边中最后一个被删去的边

    以上任意一条的任意部分不被满足, 初始时点 s 上的数字都不会最终停留在点 t; 以上所有条件都满足, 初始时点 s 上的数字最终就会停留在点 t。
    发现以上的限制都是对于 "与某个点相邻的边" 之间的限制, 自然地认为要维护这个来辅助贪心算法的判断。


    进一步的思路

    有了基本的思路, 就可以思考出算法大致的框架了。
    首先从小到大枚举数字, 找出在满足前面数字形成的限制下其可以到达的标号最小的节点, 然后把限制加上。
    最朴素的找最小节点的思路就是枚举节点, 优化这个朴素思路的方法建立在如下事实上:

    如果以当前数字的初始节点为根, 那么对于任意非根节点, 其与其父亲当作当前数字的最终节点所产生的限制是高度相似的

    那么就可以通过 dfs 来查找当前枚举到的数字能够停留的标号最小的节点。


    最终思路

    仅剩的问题是如何维护与一个点相邻的边之间的相对顺序。
    首先最终这些边的顺序一定是一个序列。
    对于 “让这条边是这个点相邻边中 第一个/最后一个 被删除的边” 这种限制, 可以加哨兵, 这样就把所有的限制都转化成 “一个边要紧接着另一个边之后删” 了。


    代码以及注释

    (时间有限, 对于代码仅做了一些最基本的注释 仅供观赏

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N = 2003;
    
    int n, fa[N], p[N], deg[N];
    
    int ct, hd[N], nt[N<<1], vr[N<<1];
        void ad(int x,int y) {nt[++ct]=hd[x], hd[x]=ct, vr[ct]=y;  }
    
    int f[N][N], t[N][N], siz[N][N]; // 用于维护删边序列(链)的并查集, 一个集合的代表元就是删边序列的头部, 用 t 记录尾部, siz 记录序列长度
        int fid(int *F, int x) {return F[x]==x ? x: F[x]=fid(F,F[x]);  }
            void mg(int x, int a, int b) { a=fid(f[x],a),b=fid(f[x],b); f[x][a]=b; t[x][b]=t[x][a]; siz[x][b]+=siz[x][a]; }
            
    int bst; //这个变量用来记录当前数字能到达的标号最小的节点
    void dfs(int x)
    {   int ff=fid(f[x],fa[x]);
        	if(fa[x])
       		{
       			int ttt=fid(f[x],n+1);
        		if( !(t[x][ff]==0 && ttt==n+1 && siz[x][ff]+siz[x][ttt]!=deg[x]+2) )
                if(ff!=ttt && t[x][ttt]==n+1 && ff==fa[x]) bst=min(bst,x);
       		}
        for(int i=hd[x],y=vr[i];i;i=nt[i],y=vr[i]) if(y!=fa[x]) {
            int tt=fid(f[x],y);
            if(t[x][ff]==0 && tt==n+1 && siz[x][ff]+siz[x][tt]!=deg[x]+2) continue;
            if( ff!=tt && ff==fa[x] && t[x][tt]==y) fa[y]=x,dfs(y);
        }
    }
    
    int main()
    {
    
        int T; scanf("%d",&T);
        while(T--)
        { scanf("%d",&n);
            for(int i=1;i<=n;++i)scanf("%d",&p[i]);
          if(n==1) //特判一下
          {   puts("1");
              continue;
          }
          ct=0;
          memset(hd,0,sizeof hd); memset(deg,0,sizeof deg);
            for(int i=1,x,y; i<n; ++i)scanf("%d%d",&x,&y), ad(x,y),ad(y,x), ++deg[x],++deg[y];
          for(int i=1;i<=n;++i)
            for(int j=0;j<=n+1;++j)
                f[i][j]=t[i][j]=j, siz[i][j]=1;
           		// 以上基本都是输入初始化
          for(int i=1;i<=n;++i)
          { fa[p[i]]= 0; bst= n+2;
              dfs(p[i]);
                cout << bst << ' ';
            int y=bst, x=fa[bst], z=n+1;
            while(y)
            { mg(y,x,z);
              z=y, y=x, x=fa[x];
            }
          }
          putchar('
    ');
        }
    
    return 0;
    }
    
  • 相关阅读:
    shell
    Python全栈开发:django网络框架(二)
    Python全栈开发:django网络框架(一)
    动态规划问题以及诸多实例分析
    python实现并查集
    使用命令行编译和运行 c、Java和python程序
    整数除法操作的取整问题
    使用TensorFlow低级别的API进行编程
    使用TensorFlow高级别的API进行编程
    TensorFlow安装和HelloWorld
  • 原文地址:https://www.cnblogs.com/tztqwq/p/13907977.html
Copyright © 2020-2023  润新知