CSP2019 树上的树 口胡

Posted tztqwq

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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;
}






以上是关于CSP2019 树上的树 口胡的主要内容,如果未能解决你的问题,请参考以下文章

CSP2019 树上的数 题解

12省联考2019口胡

JZOJ6431luoguP5658CSP-S2019括号树

CSP核心代码片段记录

CSP2019复赛游记

「csp校内训练 2019-10-30」解题报告