[ARC095 F]Permutation Tree

最近刷AtCoder,觉得刷得挺爽的,每次的F题都有一定难度,但是却还是挺可做的>_<反正AtCoder对于我这种英文不好的选手来说非常友善,看一眼就读完题面了

题意

Takahashi有一种能力以以下步骤用一个排列\((p_1,p_2,\cdots,p_n)\)来生成一棵树:

首先,按照以下操作准备顶点\(1\cdots N\)。对于顶点\(i\)

  • 如果\(p_i=1\),什么都不做
  • 如果\(p_i\neq 1\),让\(j'\)表示最大的满足\(p_j<p_i\)\(j\)。在\(j'\)\(i\)之间连一条边

给出按照这个过程建立的一棵树,判定是否能够用另外一个排列做出一棵与此同构的树,问这个要求字典序最小的排列应该是什么,无解输出\(-1\)
\(1\leq n\leq10^5\)

分析

考虑我们将这个操作的过程改写成:一开始全是黑点,然后我们将\(p_i\)按照升序排列,考虑为原来的\(i\)。然后我们令一个\(max=-1\),考虑从左到右扫一遍,如果发现\(i>max\),那么将\(i\)染红,并将\(max\)替换成\(i\);如果当前的\(p_i\neq 1\),那么连边(注意一下,原来的过程那里,\(j\)并不一定小于\(i\)……这里是对于全局的……)。
这个过程符合原来的性质,因为每次和某个点连接的红点,肯定是比\(p_i\)小,而且最接近。这时候我们会发现,我们得到的这个红点之间的链,相当于一条“直径”的东西,然后剩下的黑点都向着这个直径上连边,而且显然黑点之间没有边。
实际上按照原来的过程也可以感受出来,某一个\(p_i\)很小但是\(i\)很大的点,可能会能够“占领”一大堆点,直到某一个\(i\)更大的点出现。
这样的图是一个“毛虫图”。如果给定的图不是一个“毛虫图”,那肯定不对,这和我们刚刚证明的不一样。怎么验证呢?我们看这条链,很容易想到就是找直径,然后我们检测一下就好了,对于直径上的点每个都搜上一层,看看它是不是只会连到直径这一个点上。
那么怎么构造字典序最小的答案?事实上这也很简单。考虑设直径上的点伸出来的边依次为\(k_i\)条,不难看出此时我们只要将排列\({1,2,3\cdots l}\)做以下操作:每次对于一个\(i\),将\(\sum\limits_{j=0}^{i+1}(k_i+1)\)位置的数右移\(k_i\)个的位置,就会得到字典序最小的答案。
不过注意我们直径上面点的顺序可能有两种,都需要试一下。

# include<bits/stdc++.h>
# define rep(i,l,r) for(int i=l;i<=r;++i)
using namespace std;
const int N=1e5+5;
vector<int>lk[N];
int fa[N],deep[N];
int s[N];
int np[N],p1[N],p2[N];
 
void dfs(int x,int fr){
    fa[x]=fr;deep[x]=deep[fr]+1;
    for(auto y:lk[x])
    if(y!=fr)dfs(y,x);
}
 
int main(){
    int n;
    cin>>n;
    rep(i,1,n){
        int x,y;
        scanf("%d%d",&x,&y);
        lk[x].push_back(y);lk[y].push_back(x);
    }
    dfs(1,0);
    int x=1;
    rep(i,2,n)
    if(deep[i]>deep[x])x=i;
    dfs(x,0);
    int y=1;
    rep(i,2,n)
    if(deep[i]>deep[y])y=i;
    for(int i=y;i;i=fa[i])s[i]=1;
    rep(i,1,n)
    if(!s[i]&&++s[fa[i]]==1){
        puts("-1");
        exit(0);
    }
    vector<int>a;
    for(int i=y;i;i=fa[i])a.push_back(s[i]);
    int top=0;
    for(auto i:a){
        np[top+1]=top+i;
        rep(j,1,i-1)np[top+1+j]=top+j;
        top+=i;
    }
    rep(i,1,n)p1[np[i]]=i;
    top=0;
    for(auto it=a.end();--it>=a.begin();){
        int i=*it;
        np[top+1]=top+i;
        rep(j,1,i-1)np[top+1+j]=top+j;
        top+=i;
    }
    rep(i,1,n)p2[np[i]]=i;
    int i=1;
    while(p1[i]==p2[i]&&i<=n)++i;
    if(p1[i]<p2[i])
        rep(i,1,n)printf("%d ",p1[i]);
    else
        rep(i,1,n)printf("%d ",p2[i]);
}

转载于:https://www.cnblogs.com/wendavid/p/8871654.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值