zoukankan      html  css  js  c++  java
  • C. Helga Hufflepuff's Cup 树形dp 难

    C. Helga Hufflepuff's Cup

    这个题目我感觉挺难的,想了好久也写了很久,还是没有写出来。

    dp[i][j][k] 代表以 为根的子树中共选择了 j 个特殊颜色,且当前节点 i 的状态为 k 的染色方案数。

    1. k=0 ,代表当前节点 i 的颜色值小于 K 。
    2. k=1,代表当前节点 i 的颜色值等于 K 。
    3. k=2,代表当前节点 i 的颜色值大于 K 。

    但是这个dfs过程的处理我觉得很复杂。

    我们需要一个数组来进行临时的存储。

    tmp[i][k] 表示选了 i 个  状态为 j 的方案数。

    先枚举这个点已经用了 i 个,然后枚举这个子节点可以加上 j 个

    tmp[j + h][0] += dp[u][j][0] * (dp[v][h][0] + dp[v][h][1] + dp[v][h][2]) % mod;
    tmp[j + h][0] %= mod;
    tmp[j + h][1] += dp[u][j][1] * dp[v][h][0] % mod;
    tmp[j + h][1] %= mod;
    tmp[j + h][2] += dp[u][j][2] * (dp[v][h][0] + dp[v][h][2]) % mod;
    tmp[j + h][2] %= mod;

    然后每次再赋值回去,注意这个tmp的定义,tmp[i,j]是k有 i 个,状态为 j 的方案。 

    这个地方我觉得挺难想的,我是没有想到。

    这个是对于每一个节点u,我们枚举它的子树,一开始这个dp[u] 的初始化要注意,

    我们先考虑如果子树没有k,这个状态是怎么样的,因为只有这个状态我们是可以定下来的,如果有k,这种状态必须通过转移得来。

    然后再枚举子树k的个数,再去更新这个节点u。

    这里用了一点点的乘法原理,挺厉害的,我一开始想的是,枚举这个u的所有可能的k的数量,然后再去用背包,这个好像有点不对。

    因为我们不是取最大的那个,而是取求组合数,应该枚举每一种可能,然后相乘,也有可能是我想的不太对。

    #include <cstdio>
    #include <cstring>
    #include <cstdlib>
    #include <algorithm>
    #include <queue>
    #include <vector>
    #include <iostream>
    #include <string>
    #define inf 0x3f3f3f3f
    #define inf64 0x3f3f3f3f3f3f3f3f
    using namespace std;
    const int maxn = 2e5 + 10;
    const int mod = 1e9 + 7;
    typedef long long ll;
    struct node {
        int v, nxt;
        node(int v = 0, int nxt = 0) :v(v), nxt(nxt) {}
    }ex[maxn];
    int head[maxn], cnt = 0;
    int n, m, k, x;
    void init()
    {
        memset(head, -1, sizeof(head));
        cnt = 0;
    }
    void add(int u,int v)
    {
        ex[cnt] = node(v, head[u]);
        head[u] = cnt++;
        ex[cnt] = node(u, head[v]);
        head[v] = cnt++;
    }
    int num[maxn];
    ll tmp[20][3];
    ll dp[maxn][20][3];//注意这个dp的定义,dp[i,j,x]表示到i这个节点,子树用k的数量为j,x==0 表示这个节点小于k,1代表等于k,2代表大于k
    void dfs(int u,int pre)
    {
        dp[u][0][0] = k - 1;
        dp[u][1][1] = 1;
        dp[u][0][2] = m - k;
        num[u] = 1;
        for (int i = head[u]; i != -1; i = ex[i].nxt) {
            int v = ex[i].v;
            if (v == pre) continue;
            dfs(v, u);
            memset(tmp, 0, sizeof(tmp));
            for (int j = 0; j <= num[u]; j++) {
                for (int h = 0; h <= num[v]; h++) {
                    if (j + h > x) continue;
                    tmp[j + h][0] += dp[u][j][0] * (dp[v][h][0] + dp[v][h][1] + dp[v][h][2]) % mod;
                    tmp[j + h][0] %= mod;
                    tmp[j + h][1] += dp[u][j][1] * dp[v][h][0] % mod;
                    tmp[j + h][1] %= mod;
                    tmp[j + h][2] += dp[u][j][2] * (dp[v][h][0] + dp[v][h][2]) % mod;
                    tmp[j + h][2] %= mod;
                }
            }
            num[u] = min(x, num[u] + num[v]);
            for (int j = 0; j <= num[u]; j++) {
                for (int h = 0; h < 3; h++) {
                    dp[u][j][h] = tmp[j][h];
                }
            }
        }
    }
    
    int main()
    {
        init();
        scanf("%d%d", &n, &m);
        for(int i=1;i<=n-1;i++)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            add(u, v);
        }
        ll ans = 0;
        scanf("%d%d", &k, &x);
        dfs(1, -1);
        for (int i = 0; i <= x; i++) {
            for (int j = 0; j < 3; j++) {
                ans = (ans + dp[1][i][j]) % mod;
            }
        }
        printf("%lld
    ", ans);
        return 0;
    }
    View Code
  • 相关阅读:
    php 将英文引号成对转换为中文引号
    centos 6.2 x86_64 编译安装 httpd2.4.2时,apr报错
    PHP在通过非HTTP方式或多客户端的情况下,session的共享
    用c链接mysql
    多进程和多线程有什么区别
    进程和线程的区别
    linux中重要数据声明
    春节后返校第三天
    窗外下着雨——来到南京的第一篇
    中断门与陷阱门的区别
  • 原文地址:https://www.cnblogs.com/EchoZQN/p/11406265.html
Copyright © 2011-2022 走看看