[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
",ans);
return 0;
}