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
浙公网安备 33010602011771号