题解:P11425 [清华集训 2024] 路南柯

· · 题解

更好的阅读体验

深刻啊。

这个拓扑序看着很奇怪,我们把它变成一个我们熟悉的东西,dfs 序。注意到把 dfs 序 reverse 一下就可以得到一个剥叶子的序列。所以接下来构造的都是 dfs 序,最后倒着输出就可以。

首先考虑菊花的情况,因为这个情况可能给我们后续的讨论捣乱。假设以 1 为根,那么显然一个排列确定不了这棵树,所以两个排列就是最优的了。我们构造 \{1, 2, 3, \cdots, n\},还有 \{1, n, n-1, \cdots, 2\},那么由这两个排列我们可以知道 2 \sim n 两两不存在父子关系,所以一定都是 1 的儿子。我们就构造好了。

对于一般树,我们发现这个 dfs 序有用的性质也不多,也就是一个点的父亲一定在这个点前面出现。

考虑图示情况,我们希望分辨结点 u 的父亲 f 和爷爷 g。我们需要找到一个根开始 dfs。

不难发现,如果根在 g 子树外,那么一定是先遍历到 g 再遍历到 u;如果在 f 子树内,那么一定是先遍历到 f 再遍历到 g。那么如果我们可以在两个区域内各自选择一个点开始跑 dfs,就可以从遍历顺序相反的一对点中找到这一对父子。

考虑极端情况:我们希望分辨一个叶子和他的父亲。那么我们要从叶子的父亲子树之外选点 dfs,也要从叶子本身开始选点 dfs。有个很显然的事情,那就是对于每个点子树内和子树外一定都有叶子。所以我们可以直接从每个叶子开始 dfs,记录下 dfs 序输出。

然而这不是最优的。我们发现分辨叶子和父亲其实不需要对每个叶子进行 dfs。

发现这个局部长得很像一个菊花。所以我们可以在这里沿用菊花的做法,就是我们不分别从 a, b, c, d 开始 dfs,而是只从 f 开始 dfs 一遍。但是这样就无法分辨 a, b, c, df 的父亲。根据菊花的做法,我们可以在第一次遍历到 f 的时候以 a, b, c, d 的顺序遍历其儿子,第二次遍历的时候就以 d, c, b, a 的顺序遍历其儿子。这样 a, b, c, d 必没有父子关系,都是 f 的儿子。如此就可以分辨了。

答案就是叶子的父亲个数。为啥这个是答案下界呢?因为如果对于一个叶子,它不选,它的父亲也不选,那么永远无法分辨它和它父亲了。

按照如上方法构造,复杂度 O(n^2)

#include<bits/stdc++.h>
#define endl '\n'
#define N 106
using namespace std;
int n,ans,ord[N],vis[N],dfs_clock; vector<int> G[N];
void dfs(int u,int fa,int rt)
{
    ord[++dfs_clock]=u; vector<int> leaf;
    for(int v:G[u])
        if(v!=fa&&G[v].size()==1)leaf.push_back(v);
    if(vis[u])reverse(leaf.begin(),leaf.end()); vis[u]=1;
    for(int v:leaf)ord[++dfs_clock]=v;
    for(int v:G[u])
        if(v!=fa&&G[v].size()>1)dfs(v,u,rt);
}
void solve()
{
    scanf("%d",&n),ans=0;
    for(int i=1;i<=n;i++)G[i].clear(),vis[i]=0;
    for(int i=1,u,v;i<n;i++)
        scanf("%d%d",&u,&v),G[u].push_back(v),G[v].push_back(u);
    for(int i=1;i<=n;i++)if(G[i].size()==n-1)
    {
        printf("2\n");
        for(int j=1;j<=n;j++)if(j!=i)printf("%d ",j);
        printf("%d\n",i);
        for(int j=n;j;j--)if(j!=i)printf("%d ",j);
        return printf("%d\n",i),(void)0;
    }
    for(int i=1;i<=n;i++)if(G[i].size()>1)
    {
        int cnt=0;
        for(int j:G[i])if(G[j].size()>1)cnt++;
        ans+=(cnt==1);
    }
    printf("%d\n",ans);
    for(int i=1;i<=n;i++)if(G[i].size()>1)
    {
        int cnt=0;
        for(int j:G[i])if(G[j].size()>1)cnt++;
        if(cnt^1)continue; dfs_clock=0,dfs(i,0,i);
        for(int j=n;j;j--)printf("%d%c",ord[j]," \n"[j==1]);
    }
}
main()
{
    int T; scanf("%d",&T);
    while(T--)solve();
    return 0;
}