gmoj 6838. 【2020.10.31提高组模拟】小j的组合

题目

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;
}

猜你喜欢

转载自blog.csdn.net/huangzihaoal/article/details/109408688