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

 

原文地址:https://www.cnblogs.com/xgtao/p/6001688.html