• 树上染色+可怜与超市(树状DP)


    这两道题是学长精心准备的,想了很长时间,比较经典。

    第一题 树上染色

    有一棵点数为 N的树,树边有边权。给你一个在 0∼N之内的正整数 K,你要在这棵树中选择 K 个点,将其染成黑色,并将其他的 N−K个点染成白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间距离的和的收益。

    问收益最大值是多少。

    这一题一开始没思路主要是卡在了在从下向上转移中,如何将每个儿子的贡献值的算上

    后来怂了题解QAQ

    这题是树状背包问题

    主要难点在于如何将每条边数加上,

    设当前的节点为x而儿子节点为to

    那么我们可以发现当前数组f[x][j]中包含的是x之前的儿子所做的贡献

    这是树状背包常考虑的事情那么我们为了求当前的to所做的贡献

    只需考虑当前to与x之间的边所做的贡献

    (至于证明,显然此时f[x][j+k]=max(f[x][j+k],f[x][j]+f[to][k]+........)中f[x][j]是已转移过的,而此时需要将新加子树算进,这样枚举完x的子树后就是正确的。)

     1 #include <iostream>
     2 #include <cstdio>
     3 #include <cstring>
     4 #include <string>
     5 #include <cmath>
     6 #include <queue>
     7 #include <stack>
     8 #include <vector>
     9 #define MAXN 5001
    10 #define pt printf("-----------
    ");
    11 #define push_back ps
    12 #define ll long long
    13 using namespace std;
    14 struct node {
    15     ll to, n, w;
    16 } e[MAXN];
    17 ll head[MAXN], tot;
    18 void add(ll u, ll v, ll w) {
    19     e[++tot].to = v;
    20     e[tot].n = head[u];
    21     e[tot].w = w;
    22     head[u] = tot;
    23 }
    24 ll f[MAXN][MAXN];
    25 ll size[MAXN];
    26 ll n, K;
    27 bool bian[MAXN];
    28 void DFS(ll x) {
    29     bian[x] = 1;
    30     size[x] = 1;
    31     f[x][0]=f[x][1]=0;
    32     for (ll i = head[x]; i; i = e[i].n) {
    33         ll to = e[i].to;
    34         if (bian[to] == 1)
    35             continue;
    36         bian[to] = 1;
    37         DFS(to);
    38         size[x] += size[to];
    39         for (ll j = min(K, size[x]); j >= 0; --j) {
    40             for (ll k = 0; k <= j; ++k) {
    41                 if (k > size[to])
    42                     break;
    43                 f[x][j] =
    44                     max(f[x][j], f[x][j - k] + f[to][k] +
    45                                      e[i].w * (k * (K - k) + (size[to] - k) * (n - K - (size[to] - k))));
    46                 // printf("size[%lld]=%lld size[%lld]=%lld 黑zi=%lld 黑qi=%lld 白zi=%lld
    47                 // 白qi=%lld
    ",to,size[to],x,size[x],k,K-k,size[to]-k,n-K-(size[to]-k));
    48                 // printf("---f[%lld][%lld]=%lld f[%lld][%lld]=%lld
    ",x,j-k,f[x][j-k],to,k,f[to][k]);
    49             }
    50             // printf("f[%lld][%lld]=%lld
    ",x,j,f[x][j]);
    51         }
    52     }
    53 }
    54 ll root = 1;
    55 ll ru[MAXN];
    56 int main() {
    57     memset(f,-0x3f,sizeof(f));
    58     scanf("%lld%lld", &n, &K);
    59     for (ll i = 1; i <= n - 1; ++i) {
    60         ll x, y, w;
    61         scanf("%lld%lld%lld", &x, &y, &w);
    62         add(x, y, w);
    63         add(y, x, w);
    64     }
    65     // DFS_S(root);
    66     memset(bian, 0, sizeof(bian));
    67     DFS(root);
    68     ll ans = 0;
    69         ans = max(ans, f[root][K]);
    70     printf("%lld
    ", ans);
    71 }
    View Code

    第二题

    一道不错的树状DP

    有几个限制条件n个物品,各有价值,各有优惠券,除一外都有一个使用的条件,问钱b买多少商品。

    这题我们不直接求结果,用f[x][y][2],表示第x点买y个物品的最小值,1表示用券,0则反。

    那么最后一位是1时比较好处理

    因为x必选,所以f[x][j+k][1]=min(f[x][j+k][1],f[x][j][1]+min(f[to][k][1],f[to][k][0]);

    可以提前给f[x][1][1]加上c[x]-d[x],这样转移方程中就不会加重。

    而为0时,f[x][j+k][0]=min(f[x][j+k][0],f[x][j][0]+f[to][k][0])

    因为x可能选

    最后再用for从1~size[x]用f[x][i-1]+c[i]更新f[x][i]最小值

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<string>
     5 #include<algorithm>
     6 #include<cmath>
     7 using namespace std;
     8 #define MAXN 6000
     9 int head[MAXN],tot;struct node{int to,n;}e[MAXN];
    10 void add(int u,int v){e[++tot].to=v;e[tot].n=head[u];head[u]=tot;}
    11 int f[MAXN][MAXN][2];int n,c[MAXN],d[MAXN];
    12 int deep=1;
    13 int size[MAXN];
    14 void DFS(int x)
    15 {
    16     size[x]=1;
    17     f[x][1][1]=c[x]-d[x];f[x][0][0]=0;
    18     for(int i=head[x];i;i=e[i].n)
    19     {
    20         int to=e[i].to;
    21         DFS(to);
    22        // printf("---------%d
    ",to);
    23        // printf("deep=%d
    ",deep);
    24         for(int j=size[x];j>=0;--j)
    25         { 
    26             for(int k=size[to];k>=0;--k)
    27             {
    28                f[x][j+k][1]=min(f[x][j+k][1],f[x][j][1]+min(f[to][k][1],f[to][k][0]));
    29                f[x][j+k][0]=min(f[x][j+k][0],f[x][j][0]+f[to][k][0]);
    30          //      printf("f[%d][%d][1]=%d f[%d][%d][0]=%d
    ",x,j,f[x][j+k][1],x,j,f[x][j+k][0]);                   
    31             }
    32         } 
    33         size[x]+=size[to];
    34     } 
    35     for(int i=size[x];i>=1;--i)
    36     {
    37         f[x][i][0]=min(f[x][i][0],f[x][i-1][0]+c[x]);
    38     }
    39 }
    40 int b;
    41 int main()
    42 {
    43    scanf("%d%d",&n,&b);
    44    memset(f,0x3f,sizeof(f));  
    45    for(int i=1;i<=n;++i)
    46    {
    47       int x;
    48       if(i==1)
    49       scanf("%d%d",&c[i],&d[i]);
    50       else 
    51       {
    52           scanf("%d%d%d",&c[i],&d[i],&x);
    53           add(x,i);
    54       }
    55       f[i][1][1]=c[i]-d[i];
    56    }   
    57    int root=1;
    58    DFS(root);
    59    int x;
    60    for(x=1;x<=n;++x)
    61    {
    62        if(f[1][x][1]>b&&f[1][x][0]>b)break;
    63    }
    64    printf("%d
    ",x-1);
    65 }
    View Code

    树状DP注意点:

    1.首先树状DP大部分情况都是逆推,用根节点统计答案。

    2.在做树状背包时一定要注意不能单纯用f[to]更新f[x],因为这样枚举不全,只考虑了一棵子树

    所以一般两层循环枚举第二维来更新

    3注意倒序,不要重复更新。

    最后感谢kx解决我做题的傻逼错误。

  • 相关阅读:
    python排序
    python中常用的九种数据预处理方法分享
    8089汇编 源程序
    8086汇编 栈操作
    8089汇编 运算符指令
    8089汇编 标志寄存器
    8086汇编 段寄存器
    8086汇编 Debug 使用
    8086汇编 CPU 寄存结构
    8086汇编 内存交互
  • 原文地址:https://www.cnblogs.com/Wwb123/p/11203904.html
Copyright © 2020-2023  润新知