k叉树问题
题目:知山知水,树木树人 - 计蒜客 A1211 - Virtual Judge (vjudge.net)
有一个完全k叉树,每个节点有 k 条边,权值一次为1~k。
求从起点1号开始,走到过的边权值总和为n,且有一边权 >= d的路线数。
对于线性DP问题,我们需要得到最终符合要求的结果,即 满足权值为 n,至少经过一边权值 >= d 的路线数。
类似于背包,公共子序列等等,状态的设置可以根据 最终结果所需要满足的条件来设置。
例如:
对比可发现,k叉树问题不需要找到状态的属性,没有最大值最小值要求,求的是数量。
同公共子序列的分析思路一样,对于状态计算,我们只从结果切入考虑,对于 f[i][0] 状态,表示为当前权值为 i,且遇到过 >= d 。
那么它可以从 权值总和为 [i - k , i - 1]
的状态经过一条边转移过来,且没遇到 >= d 的边,可以表示为 f[i - k ~ i - 1][0]
,则状态转移方程为 f[i][0] = f[i][0] + f[i - k ~ i - 1][0]
。
为什么是从 [i - k , i - 1]
转移过来呢?因为从 f[i][0] 的上一个节点走到该节点时,权值只能为 1 ~ k,并且每个权值边都有可能被走。
对于f[i][1],有两种可能,需要加个判断条件 if(j >= d) j 为当前权值
。
对于第一种,当前权值>= d是,不论是否之前有过没有过 >= d 都符合要求,状态转移方程为 f[i][1] = f[i][1] + f[i - k ~ i - 1][1] + f[i - k ~ i - 1][0]
。
对于第二种,当前权值 <= d, 若之前有过则状态转移方程为 f[i][1] = f[i][1] + f[i - k ~ i - 1][0]
, 若没有过则 f[i][0] = f[i][0] + f[i-k ~ i-1][0]
。
故最终转移方程为
if(j >= d) f[i][1] = f[i][1] + f[i-k ~ i-1][0] + f[i-k ~ i-1][1];
else {
f[i][0] = f[i][0] + f[i-k ~ i-1][0];
f[i][1] = f[i][1] + f[i-k ~ i-1][1];
}
AC代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110, MOD = 1e9 + 7;
long long f[N][2]; // 对 1e9 + 7 取模,可能会爆int
int n, k, d;
int main()
{
while(cin >> n >> k >> d) // 注意是多组数据,虽然不知道为啥题上没说
{
memset(f, 0, sizeof f);
f[0][0] = 1;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= min(i, k); j++) // 不能超出最大权值,和当前权值和
{
if (j >= d)
f[i][1] = (f[i][1] + f[i - j][1] + f[i - j][0]) % MOD;
else
{
f[i][1] = (f[i][1] + f[i - j][1]) % MOD;
f[i][0] = (f[i][0] + f[i - j][0]) % MOD;
}
}
cout << f[n][1] << endl;
}
return 0;
}