题意:
给出一棵以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