• label


    题意:

    给出一棵以1为根节点包含n(<=100)个节点的树,然后在每个节点填上一个范围在[1,m(<=1e9)]的一个数字,使得任意两个节点之间的差的绝对值大于等于K(<=100),求方案数。

    题解:

    很显然是树形dp,定义状态dp[u][i]表示在u这个节点填上i这个数的这棵子树的方案数。

    考虑转移:

    复杂度为 N * M * M :TLE

    但是会发现i可取的值的范围是连续的一段,那么可以维护一个前缀和就可以不用枚举i了。 复杂度为 N*M :TLE

    考虑一条链的情况,从根节点一直到叶子节点,很显然叶子节点的dp值全部为1,

    那么往上返回的时候,令叶子节点的父节点的编号为u,会发现dp[u][1~K-1]的值对应dp[u][m~m-K+1],然后dp[u][K~m-K]的值全部相等。

    嗯再往上返回的时候,令其节点的父亲节点的编号为u,会发现dp[u][1~2*(K-1)]的值对应dp[u][m~m-2*(K-1)],然后dp[u][2*K-1,m-2*K+1]的值全部相等

    ………………………………(这个可能需要自己手推一下)

    然后就会发现dp[u][1~(n-1)*(K-1)]对应于dp[u][m~m-(n-1)*(K-1)],然后dp[u][(n-1)*(K-1)+1 ~ m-(n-1)*(K-1)-1]的值全部相等。

    现在,发现这个性质之后,观察(n-1)*(K-1)的大小只有1W,只需要关心前面(n-1)*(K-1)个数,中间的数,后面的(n-1)*(K-1)个数会大大降低复杂度,然后会得到这样的一个dp函数图像

    前缀有lim-1个数,那么自然后缀的个数也为lim-1,那么中间相等的个数为m - 2*lim + 2,考虑维护前缀和,后缀和,所以现在只需要考虑i在lim范围只能选值,因为算出这些值其余的值都可以由这些值得到。

    情况一:

    那么这时的dp值应为(pre[i-K]) + (pre[lim-1] - pre[i+K-1]) + (m - 2*lim + 2) * mid[lim] + pre[lim-1],也就是说除了[i-K,i+K]的部分的dp值之和。

    情况二:

    那么这时的dp值应该为pre[i-K] + (lim - i - K + 1) * mid[lim] + pre[lim-1]

    情况三:

    那么这时的dp值应该为pre[i-K] + pre[m-i-K+1]

    然后就OK了~

    代码:

    #include <cstdio>
    #include <iostream>
    #include <vector>
    #include <cstring>
    #include <algorithm>
    using namespace std;
     
    const int N = 1e2 + 7;
    const int M = 1e4 + 7;
    const int mod = 1e9 + 7;
    #define ll long long
    vector <int> e[N];
    int lim, K, n, m, kase, ans;
    ll mid[N][M], pre[N][M], suf[N][M];
     
    void Dfs (int u, int father) {
        for (int i = 0; i < e[u].size(); ++i) {
            if (e[u][i] != father) Dfs (e[u][i], u);
        }
        for (int i = 1; i <= lim; ++i) mid[u][i] = 1;
        for (int i = 1; i <= lim; ++i) {
            for (int j = 0; j < e[u].size(); ++j) {
                int v = e[u][j];
                if (v == father) continue;
                ll sum = 0;
                if (i - K >= 1) sum = (sum + pre[v][i-K]) % mod;
                if (i + K <= lim) {
                    sum = (sum + suf[v][i+K]) % mod;
                    sum = (sum + pre[v][lim-1]) % mod;
                    sum = (sum + mid[v][lim] * (m - 2 * lim + 1)) % mod;
                }
                else if (i + K <= m) {
                    if (i + K <= m - lim + 1) {
                        sum = (sum + pre[v][lim-1]) % mod;
                        sum = (sum + mid[v][lim] * (m - lim - i - K + 2)) % mod;
                    }
                    else sum = (sum + pre[v][m - K - i + 1]) % mod;
                }
                if (K == 0) sum = (sum - mid[v][i] + mod) % mod;
                mid[u][i] = (mid[u][i] * sum) % mod;
            }
        }
        for (int i = 1; i <= lim; ++i) pre[u][i] = (mid[u][i] + pre[u][i-1]) % mod;
        for (int i = lim; i >= 1; --i) suf[u][i] = (mid[u][i] + suf[u][i+1]) % mod;
    }
     
    int main () {
        scanf ("%d", &kase);
        while (kase--) {
            for (int i = 1; i <= n; ++i) e[i].clear();
            memset (suf, 0, sizeof suf);
            memset (pre, 0, sizeof pre);
            scanf ("%d%d%d", &n, &m, &K);
            for (int i = 1; i < n; ++i) {
                int u, v;
                scanf ("%d%d", &u, &v);
                e[u].push_back(v);
                e[v].push_back(u);
            }
            lim = min (10000, m / 2 + (m & 1));
            Dfs (1, 0);
            ll ans = 0;
            ans = (ans + pre[1][lim-1]*2) % mod;
            ans = (ans + (m - 2 * lim + 2) * mid[1][lim]) % mod;
            cout << ans << endl;
        }
        return 0;
    }
    

      

    总结:

    做树形dp的时候一般先考虑链的特殊情况吧~然后真的要动笔算一算,其实主要就是发现这个性质,减少冗余计算,好坑呀有木有,被玩坏了QAQ

     

  • 相关阅读:
    Android调用WebService
    webKit和chromium的文章地址
    关注web前端
    第三次面向对象程序设计作业
    第二次面向对象程序设计作业
    面向对象程序作业一
    HashMap的存储原理
    关于MySql中使用IFNULL()函数失效的问题。
    利用反射操作bean的属性和方法
    几种String对象方法的区别
  • 原文地址:https://www.cnblogs.com/xgtao/p/6001688.html
Copyright © 2020-2023  润新知