题目
https://gmoj.net/senior/#main/show/6838
题解
这道题目看起来比较奇怪,于是比赛的时候我就没有管它了。
考虑一个点在最终的图中分裂了几次,发现分裂次数等于经过它的次数-1。以起点为根,所走的路径就是走去很多个子树内绕一圈回来,然后选择一个儿子走下去就不回来,在这个儿子处也是这样子走。
怎么样才能让不返回的点尽可能多呢(显然一个点返回它的父亲处时,它的父亲要分裂一次)?这等价于选择一条树上最长简单路径,即直径。
因此这道题只用找出直径,以它的一个端点为根走,然后对于直径上的每一个点,都先将它的非直径上儿子的子树走完并返回,接着顺着直径往下走。
CODE
#include<cstdio>
using namespace std;
#define M 205
#define N 105
int f[N][2],son[N][2],fir[N],to[M],nex[M],p[100005],now[N],pre[N],len,node,s;
bool b[N];
inline void inc(int x,int y)
{
to[++s]=y,nex[s]=fir[x],fir[x]=s;
to[++s]=x,nex[s]=fir[y],fir[y]=s;
}
void getd(int k,int fa)
{
for(int i=fir[k];i;i=nex[i]) if(to[i]!=fa)
{
getd(to[i],k);
if(f[to[i]][0]+1>f[k][0])
f[k][1]=f[k][0],son[k][1]=son[k][0],
f[k][0]=f[to[i]][0]+1,son[k][0]=to[i];
else if(f[to[i]][0]+1>f[k][1])
f[k][1]=f[to[i]][0]+1,son[k][1]=to[i];
}
if(f[k][0]+f[k][1]>len) len=f[k][0]+f[k][1],node=k;
}
void dfs(int k,int fa)
{
p[++p[0]]=k;
for(int i=fir[k];i;i=nex[i]) if(to[i]!=fa&&to[i]!=pre[k])
dfs(to[i],k),p[++p[0]]=k;
if(pre[k]) dfs(pre[k],k);
}
int main()
{
freopen("combo.in","r",stdin);
freopen("combo.out","w",stdout);
int root,n,x,y,cnt;
scanf("%d",&n),cnt=n;
for(int i=1;i<n;++i) scanf("%d%d",&x,&y),inc(x,y);
getd(1,0),root=node,b[root]=1;
while(son[root][0]) pre[son[root][0]]=root,root=son[root][0],b[root]=1;
if(son[node][1])
{
pre[node]=x=son[node][1],b[x]=1;
while(son[x][0]) x=pre[x]=son[x][0],b[x]=1;
}
dfs(root,0);
printf("%d\n",p[0]-n);
for(int i=1;i<=p[0];++i)
{
if(!now[p[i]]) now[p[i]]=p[i];
else printf("%d ",now[p[i]]),p[i]=now[p[i]]=++cnt;
}
puts("");
for(int i=1;i<=p[0];++i) printf("%d ",p[i]);
puts("");
return 0;
}