题目链接
https://codeforces.com/contest/431/problem/C
题目描述
最近有一个富有创造力的学生Lesha听了一个关于树的讲座。在听完讲座之后,Lesha受到了启发,并且他有一个关于k-tree(k叉树)的想法。
k-tree都是无根树,并且满足:
- 每一个非叶子节点都有k个孩子节点;
- 每一条边都有一个边权;
- 每一个非叶子节点指向其k个孩子节点的k条边的权值分别为1,2,3,…,k。
当Lesha的好朋友Dima看到这种树时,Dima马上想到了一个问题:“有多少条从k-tree的根节点出发的路上的边权之和等于n,并且经过的这些边中至少有一条边的边权大于等于d呢?”
现在你需要帮助Dima解决这个问题。考虑到路径总数可能会非常大,所以只需输出路径总数 mod 1000000007 即可。(1000000007=10^9+7)
解题思路
这道题有点类似于以前做过的一道搜索题,就是把 n n n 分解成多个数相加的不同方案数,不过这道题加了一些限制条件,即相加的数只能使用 [ d , k ] [d,k] [d,k]范围内的数。
1-样例分析
以 n = 4 , k = 3 , d = 2 n=4,k=3,d=2 n=4,k=3,d=2为例,列出所有方案数,符合要求的总共有6种:
- 4=1+1+1+1 (不合法)
- 4=1+1+2(3种)
- 4=1+3(2种)
- 4=2+2(1种)
- 4=4(不合法)
2-化繁为简
分析问题时,我们可以把细枝末叶去掉,只保留主要的框架。这个问题的限制条件为
k
k
k和
d
d
d,假设
k
=
n
,
d
=
1
k=n,d=1
k=n,d=1,可得到(自行找个例子枚举下):
f
(
n
,
k
,
d
)
=
2
n
−
1
(
k
=
n
,
d
=
1
)
f(n,k,d)=2^{n-1} \quad (k=n,d=1)
f(n,k,d)=2n−1(k=n,d=1)
接下来处理限制条件
d
d
d,利用容斥原理 ,先算出所有的方案数,再 把
d
d
d以下方案数减掉 即可,有:
f
(
n
,
k
,
d
)
=
f
(
n
,
k
,
1
)
−
f
(
n
,
d
−
1
,
1
)
f(n,k,d)=f(n,k,1)-f(n,d-1,1)
f(n,k,d)=f(n,k,1)−f(n,d−1,1)
这样,我们只需专注于求解
f
(
n
,
k
,
1
)
f(n,k,1)
f(n,k,1)这个问题就行了!
3-状态的设计(拆分问题)
接下来处理限制条件 k k k,对数拆分时,把一个问题拆分成一个数和一个子问题相加的形式(对样例分析时观察是否包含子问题),设 f ( n ) f(n) f(n)为将 n n n拆分成多个数的方案数量,以 f ( 4 ) f(4) f(4)为例:
f ( 4 ) = 1 + f ( 3 ) f(4)=1+f(3) f(4)=1+f(3)
= 2 + f ( 2 ) =2+f(2) =2+f(2)
= 3 + f ( 1 ) =3+f(1) =3+f(1)
= 4 =4 =4
只保留边权小于等于
k
k
k的方案数,设
f
(
n
,
k
)
f(n,k)
f(n,k)为将n拆分成多个数相加方案数量,并且这些数的范围为
[
1
,
k
]
[1,k]
[1,k]。
以
f
(
4
,
2
)
f(4,2)
f(4,2)为例:
f ( 4 , 2 ) = { 1 + f ( 3 ) , 2 + f ( 2 ) } f(4,2)= \{1+f(3),2+f(2)\ \} f(4,2)={1+f(3),2+f(2) }
当然,子问题的边权肯定也不能大于k,就有:
f ( 4 , 2 ) = { 1 + f ( 3 , 2 ) , 2 + f ( 2 , 2 ) } f(4,2)= \{1+f(3,2),2+f(2,2)\ \} f(4,2)={1+f(3,2),2+f(2,2) }
4-状态转移方程
经过对样例的分析,可以和知这个问题满足重叠子问题和最优子结构,可用DP求解,状态转移方程为:
f
(
n
,
k
)
=
∑
i
=
1
k
f
(
n
−
i
,
k
)
f(n,k)=\displaystyle\sum_{i=1}^k \ f(n-i,k)
f(n,k)=i=1∑k f(n−i,k)
参考代码
计算过程用记忆化搜索实现,本质上和DP没有什么不同。
#include <iostream>
using namespace std;
const int MOD=1000000007;
const int MAXN=103;
long long dp[MAXN][MAXN];
long long power(int n){
long long s=1;
for(int i=1;i<=n;i++) {
s *= 2;
s %= MOD;
}
return s%MOD;
}
long long fun(int n,int k){
if(dp[n][k]>0) return dp[n][k];
if( k==1 ) return dp[n][k]=1;
if( k >= n ){
dp[n][k]=power( n-1)%MOD;
return dp[n][k];
}
long long s=0;
for(int i=k;i>=1;i--){
s += fun(n-i,k);
s %= MOD;
}
return dp[n][k]=s;
}
int main(){
int n,k,d;
cin>>n>>k>>d;
if( k>n ) k=n;
if( d == 1) cout<<fun(n,k) % MOD <<endl;
else cout<< (( fun(n,k)-fun(n,d-1)) % MOD+MOD)%MOD <<endl;
return 0;
}