[CQOI2017] 小Q的棋盘

[CQOI2017] 小Q的棋盘

题目链接:洛谷P3698

题意简述

给定一棵树,点数为n,从根节点出发,每一步可以走向与当前点有直接边相连的点,问走m步最多能经过多少个点。边和点均可以重复经过,但不重复计数。

算法概述

\(f[p][j]\) 表示从 \(p\) 出发走向以 \(p\) 为根的子树,一共走 \(k\) 步并且最终回到 \(p\),最多能经过的点数。

\(g[p][j]\) 表示从 \(p\) 出发走向以 \(p\) 为根的子树,一共走 \(k\) 步并且最终不回到 \(p\),最多能经过的点数。

考虑 \(f[p][j]\) 的转移,走的情况应该是从 \(p\) 出发,走向某棵子树,再走回 \(p\),再走向某棵子树,再走回 \(p\),……,最后走回 \(p\)

枚举 \(p\) 的所有子树,对于当前的子树 \(x\),考虑分配给其多少步 \(k\),由于还要走回 \(p\),故 \(k\) 的范围即是 \([0,j-2]\),状态转移方程即为:

\[f[p][j]=\mathop{max}_{2<=j<=m}\{f[p][j-k-2]+f[x][k]\} \]

边界:\(f[p][0]=1\)

考虑 \(g[p][j]\) 的转移,对于当前的子树 \(x\),走的情况无非以下两种:

  • \(p\) 出发走向其他子树最后回到 \(p\),再走向 \(x\) 的子树,最后不回来。
  • \(p\) 出发走向 \(x\) 的子树最后回到 \(p\) ,再走向 \(p\) 的其他子树,最后不回来。

对于第一种情况:

\[g[p][j]=\mathop{max}_{1<=j<=m,0<=k<=j-1}\{f[p][j-k-1]+g[x][k]\} \]

对于第二种情况:

\[g[p][j]=\mathop{max}_{2<=j<=m,0<=k<=j-2}\{g[p][j-k-2]+f[x][k]\} \]

边界:\(g[p][0]=1\)

最后的答案为 \(g[0][1 \ldots m]\) 中的最大值,因为如果不足 \(m\) 步就达到了最大值的话,剩下的步数可以随便走。

参考代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N=110;
struct Edge{
	int to,nex;
}edge[N<<1];int idx;
int h[N];

void add_edge(int u,int v){edge[++idx]={v,h[u]};h[u]=idx;}

int f[N][N];
int g[N][N];
int n,m;

void dfs(int p,int fa)
{
	f[p][0]=g[p][0]=1;
	for(int i=h[p];~i;i=edge[i].nex)
	{
		int to=edge[i].to;
		if(to==fa)continue;
		dfs(to,p);
		for(int j=m;j>=1;j--)
			for(int k=j-1;k>=0;k--)
			{
				g[p][j]=max(g[p][j],f[p][j-k-1]+g[to][k]);
				if(j-k-2>=0)
					f[p][j]=max(f[p][j],f[p][j-k-2]+f[to][k]),
					g[p][j]=max(g[p][j],g[p][j-k-2]+f[to][k]);
			}
	}
}

int main()
{
	memset(h,-1,sizeof h);
	scanf("%d%d",&n,&m);
	for(int i=1,a,b;i<=n-1;i++)
	{
		scanf("%d%d",&a,&b);
		add_edge(a+1,b+1);
		add_edge(b+1,a+1);
	}
	dfs(1,0);
	int ans=0;
	for(int i=1;i<=m;i++)ans=max(ans,g[1][i]);
	printf("%d\n",ans);
	return 0;
}
posted @ 2020-10-27 07:52  魑吻丶殇之玖梦  阅读(78)  评论(0编辑  收藏  举报