20191110luogu3698小Q的棋盘 | 树形背包dp | 贪心

luogu3698小Q的棋盘

题意:

求从树的根节点出发,走n步能经过的最多的点的数量(可以重复走点,但是重复走的步数会记录)

树形背包dp:

对于从0出发,我们可以这样走:

1、选一条岔路一直走下去

2、选一条岔路走后回到0点,再选一条岔路走下去

对应的dp转移:

f[0][u][j]代表从u出发走j步不一定回到u点能到达的最大步数

f[1][u][j]代表从u出发走j步回到u点能到达的最大步数

f[0][u][j] = max(f[0][u][j],f[0][u][k] + f[1][too][j - k - 2])//2状态,选too的路径并走回too,再走回u,从u再选一条新路径走下去

f[0][u][j] = max(f[0][u][j],f[1][u][k] + f[0][too][j - k - 1])//2状态,选某路径走个往返,再走回u,从u选择从too走下去(注意这个状态和上一个不一样,必须要讨论这两种情况)

f[1][u][j] = max(f[1][u][j],f[1][u][k] + f[1][too][j - k - 2]);//1状态

需要注意的是j一定要从大到小倒序枚举,因为我们的状态是由小到大转移的,所以为了避免重复转移状态,j从小到大枚举

还需注意的是k需要从0开始枚举,因为我们初始化f[0][u][0]= f[1][u][0] = 1,需要利用上初始状态

时间复杂度:O(vn^2)

 1 #include<cstdio>
 2 #include<algorithm>
 3 using namespace std;
 4 int to[205],head[205],nxt[205],cnt;
 5 int f[2][205][205],n,v;
 6 void add(int u,int v)
 7 {
 8     to[++cnt] = v;
 9     nxt[cnt] = head[u];
10     head[u] = cnt;
11 }
12 void dfs(int u,int fa)
13 {
14     f[0][u][0] = f[1][u][0] = 1;
15     for(int i = head[u]; i; i = nxt[i])
16     {
17         int too = to[i];
18         if(too == fa)continue;
19         dfs(too,u);
20         for(int j = n;j >= 0;j --)
21         {
22             for(int k = 0;k <= j - 1;k ++)
23             {
24                 if(j - k - 1 >= 0)f[0][u][j] = max(f[0][u][j],f[1][u][k] + f[0][too][j - k - 1]);
25                 if(j - k - 2 >= 0)
26                 {
27                     f[1][u][j] = max(f[1][u][j],f[1][u][k] + f[1][too][j - k - 2]);
28                     f[0][u][j] = max(f[0][u][j],f[0][u][k] + f[1][too][j - k - 2]);
29                 }
30                 f[0][u][j] = max(f[0][u][j],f[1][u][j]);
31             }
32         }
33     }
34     for(int i = 1;i <= n;i ++)
35     {
36         f[0][u][i] = max(f[0][u][i],f[0][u][i - 1]);
37         f[1][u][i] = max(f[1][u][i],f[1][u][i - 1]);
38 //        printf("f[0][%d][%d]=%d
",u,i,f[0][u][i]);
39     }
40 }
41 int main()
42 {
43     scanf("%d%d",&v,&n);
44     for(int i = 1,a,b; i<= v - 1; i ++)
45     {
46         scanf("%d%d",&a,&b);
47         add(a,b);
48         add(b,a);
49     }
50     dfs(0,0);
51     printf("%d",f[0][0][n]);
52     return 0;
53 }

贪心:

可以证明的是我们一定会走从0出发的最长链

因为明显最优的就是不用往回走

当n大于最长链长度时,我们再考虑往回走的问题,每多经过一个点,需要的步数+2(一来一回),直接计算即可

当n小于等于最长链长度时,直接输出步数+1(包含根节点)

时间复杂度:O(v)

原文地址:https://www.cnblogs.com/djfuuxjz/p/11831742.html