正题
题目链接:https://www.luogu.com.cn/problem/P7920
题目大意
一个排列\(p\)生成的森林的形式如下,对于每个\(i\)找到最大的\(j\in [1,i)\)满足 \(p_i>p_j\),然后连一条\(i,j\)之间的边。
给出一张树\(G\),求一个字典序最大的排列\(p\)使得生成的树与\(G\)同构。
\(1\leq n\leq 5000\)
解题思路
先考虑暴力的方法,我们可以枚举一个根,然后开始肯定根是\(1\),然后子节点中假设大小都不相同我们肯定是从小到大放,因为假设现在放到\(k\),那么每棵子树的根放的肯定是\(k-siz+1\)(因为一个根要是子树中最小的)。如果有大小相同的我们可以比较每棵内部放好时的字典序然后从大到小放。
显然这样是三方的,我们需要优化。考虑到我们每次需要比较子树的字典序大小,而对于所有根来说整棵就只有\(2n-2\)棵子树(每条边的正反方向的子树),我们可以把这些子树都拿出来比较。
先按照子树大小排序,然后我们按照子树大小从小到大排同大小子树的顺序,因为排大的子树时需要用到小子树的顺序关系。
而且不难观察到最优的根一定是与某个叶子连接的点,同样的这个叶子它的另一边是一个大小为\(n-1\)的子树,所以我们拿顺序最小的大小为\(n-1\)的子树出来输出即可。
时间复杂度:\(O(n^2\log n)\),但是由于子树的字典序排序只会出现在同大小之间,所以常数很小,可以通过本题。
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cctype>
using namespace std;
const int N=1e4+10;
struct node{
int id,siz;
vector<int> v;
}p[N];
struct edge{
int to,next,from;
}a[N];
int n,tot,ls[N],g[N],f[N],siz[N];
int read() {
int x=0,f=1; char c=getchar();
while(!isdigit(c)) {if(c=='-')f=-f;c=getchar();}
while(isdigit(c)) x=(x<<1)+(x<<3)+c-48,c=getchar();
return x*f;
}
bool cmp(int x,int y)
{return f[x]<f[y];}
bool cMp(node x,node y)
{return x.siz<y.siz;}
bool CmP(node x,node y){
int l=min(x.v.size(),y.v.size());
for(int i=0;i<l;i++)
if(f[x.v[i]]!=f[y.v[i]])
return f[x.v[i]]<f[y.v[i]];
return 0;
}
void addl(int x,int y){
a[++tot].to=y;
a[tot].from=x;
a[tot].next=ls[x];
ls[x]=tot;return;
}
void dfs(int x,int fa){
siz[x]=1;
for(int i=ls[x];i;i=a[i].next){
int y=a[i].to;
if(y==fa)continue;
dfs(y,x);siz[x]+=siz[y];
}
return;
}
void print(int x,int k){
for(int i=0;i<p[x].v.size();i++){
printf("%d ",k-p[g[p[x].v[i]]].siz+1);
print(g[p[x].v[i]],k);
k-=p[g[p[x].v[i]]].siz;
}
return;
}
int main()
{
n=read();
if(n==1)return puts("1")&0;
for(int i=1;i<n;i++){
int x=read(),y=read();
addl(x,y);addl(y,x);
}
dfs(1,0);
for(int z=1;z<=tot;z++){
int x=a[z].from,y=a[z].to;
p[z].siz=(siz[y]>siz[x])?(n-siz[x]):siz[y];
for(int i=ls[y];i;i=a[i].next){
int w=a[i].to;
if(w==x)continue;
p[z].v.push_back(i);
}
p[z].id=z;
}
sort(p+1,p+1+tot,cMp);
int l=1,r=0,cnt=0;
for(int S=2;S<n;S++){
while(l<=tot&&p[l].siz<S)l++;
while(r<tot&&p[r+1].siz<=S)r++;
for(int x=l;x<=r;x++)
sort(p[x].v.begin(),p[x].v.end(),cmp);
sort(p+l,p+r+1,CmP);
f[p[l].id]=++cnt;
if(S==n-1)break;
for(int x=l+1;x<=r;x++){
cnt+=CmP(p[x-1],p[x]);
f[p[x].id]=cnt;
}
}
for(int i=1;i<=tot;i++)g[p[i].id]=i;
printf("1 %d ",n);
print(l,n-1);
return 0;
}