P3698 [CQOI2017]小Q的棋盘(树形dp,回与不回)
题目描述:
小 Q 正在设计一种棋类游戏。
在小 Q 设计的游戏中,棋子可以放在棋盘上的格点中。某些格点之间有连线,棋子只能在有连线的格点之间移动。整个棋盘上共有 V 个格点,编号为0,1,2 … , V− 1,它们是连通的,也就是说棋子从任意格点出发,总能到达所有的格点。小 Q 在设计棋盘时,还保证棋子从一个格点移动到另外任一格点的路径是唯一的。
小 Q 现在想知道,当棋子从格点 0 出发,移动 N 步最多能经过多少格点。格点可以重复经过多次,但不重复计数。
输入格式
第一行包含2个正整数V, N,其中 V 表示格点总数,N 表示移动步数。
接下来V − 1行,每行两个数a_i,b_iai,bi,表示编号为a_i,b_iai,bi的两个格点之间有连线。
输出格式
输出一行一个整数,表示最多经过的格点数量。
输入输出样例
输入 #1
5 2 1 0 2 1 3 2 4 3
输出 #1
3
输入 #2
9 5 0 1 0 2 2 6 4 2 8 1 1 3 3 7 3 5
输出 #2
5
思路:最开始我傻傻得以为点有可能重复,我真的够傻,在哪想半天,一边毫无头绪一边又想提高题怎么可能这么复杂,我真的傻
dp[u][m][2]表示u节点给它m条边,它能访问节点的最大数量,为0表示回到该点,为1表示不回到该点
说白了就是个分组背包,只不过是在dfs回溯的时候加代码罢了
AC代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 105; struct edge { int t, nxt; }e[maxn << 1]; int hd[maxn], tot; void add(int f, int t) { e[++tot] = { t,hd[f] }; hd[f] = tot; } int n,m; int dp[maxn][maxn][2]; void dfs(int u, int f,int last) { for (int i = 0; i <=last; i++) { dp[u][i][0] = dp[u][i][1] = 1; } if (last == 0)return; for (int i = hd[u]; i; i = e[i].nxt) { int v = e[i].t; if (v == f)continue; dfs(v, u,last-1); for (int j = last; j >=1; j--) { for (int k = j-1; k >=0; k--) { //不回来,可以有两种方式松弛,1是走这个孩子回来+走之前的孩子不回来 //2是走这个孩子不回来+走之前的孩子回来 //之前做过的题居然忘了还不明所以,我真的是.... dp[u][j][1] = max(dp[u][j][1], dp[u][j - k - 1][0] + dp[v][k][1]); if (j >= k + 2){//回来的情况要多用两条边用来走去儿子和从儿子回到自身 dp[u][j][1] = max(dp[u][j][1], dp[u][j - k - 2][1] + dp[v][k][0]); //回来就前面儿子回来+当前儿子回来的最大 dp[u][j][0] = max(dp[u][j][0], dp[u][j - k - 2][0] + dp[v][k][0]); } } } } } int main() { //freopen("test.txt", "r", stdin); memset(dp, 0xf3, sizeof(dp)); scanf("%d%d", &n,&m); for (int i = 1; i < n; i++) { int a, b; scanf("%d%d", &a, &b); add(a, b); add(b, a); } dfs(0, -1,m); cout <<dp[0][m][1]<< endl; return 0; }