题解P3354 [IOI2005]Riv 河流

P3354 [IOI2005]Riv 河流
这是一道比较好的树型DP,在参考了网上的一些题解后,蒟蒻终于把它给做了出来
可以看到这道题如果只用二维表示状态明显是不够的
所以设状态\(f_{i,j,k}\)表示\(i\)为根节点,\(j\)为它的一个建有伐木场的父节点,\(k\)\(i\)与它的子树共建的伐木场的数量
考虑再开一个数组\(g_{i,j,k}\)表示\(i\)这个点建了伐木场,状态含义同\(f\)

状态转移方程结合代码讲解

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 105, M = 205;

int n, k, siz[N], f[N][N][N], g[N][N][N], sum[N], stk[N];
int head[N], ver[M], net[M], edge[M], w[N], idx, top;

void add(int a, int b, int c)
{
    ver[idx] = b, net[idx] = head[a], edge[idx] = c, head[a] = idx++;
}

void dfs(int u)
{
    stk[++top] = u;
    for (int i = head[u]; ~i; i = net[i])
    {
        int v = ver[i];
        sum[v] = sum[u] + edge[i];
        dfs(v);
        for (int j = 1; j <= top; j++)
        {   
            for (int k1 = k; k1 >= 0; k1--)//枚举u一共建的伐木场数量
            {
                f[u][stk[j]][k1] += f[v][stk[j]][0];
                g[u][stk[j]][k1] += f[v][u][0];//设置初始状态
                for (int k2 = 0; k2 <= k1; k2++)//枚举当前子树建的伐木场数量
                {
                    f[u][stk[j]][k1] = min(f[u][stk[j]][k1], f[u][stk[j]][k1 - k2] + f[v][stk[j]][k2]);
                    g[u][stk[j]][k1] = min(g[u][stk[j]][k1], g[u][stk[j]][k1 - k2] + f[v][u][k2]);
                }
            }
        }
    }
      //因为上面枚举是假设的是$f$表示没建伐木场,$g$表示建了,并且$g$中$i$这个伐木场并没有算进去,所以下面要减一
    for (int j = 1; j <= top; j++)
    {
        f[u][stk[j]][0] += w[u] * (sum[u] - sum[stk[j]]);
        for (int k1 = 1; k1 <= k; k1++)
            f[u][stk[j]][k1] = min(f[u][stk[j]][k1] + w[u] * (sum[u] - sum[stk[j]]), g[u][stk[j]][k1 - 1]);
    }
    top--;
}

int main()
{
    scanf("%d%d", &n, &k);
    memset(head, -1, sizeof(head));
    for (int i = 1; i <= n; i++)
    {
        int c, v;
        scanf("%d%d%d", &w[i], &v, &c);
        add(v, i, c);
    }
    dfs(0);
    printf("%d", f[0][0][k]);
    return 0;
}
posted @ 2021-02-05 09:13  DSHUAIB  阅读(106)  评论(0编辑  收藏  举报