CF431C - k-Tree

题目链接

https://codeforces.com/contest/431/problem/C

题目描述

最近有一个富有创造力的学生Lesha听了一个关于树的讲座。在听完讲座之后,Lesha受到了启发,并且他有一个关于k-tree(k叉树)的想法。
k-tree都是无根树,并且满足:

  1. 每一个非叶子节点都有k个孩子节点;
  2. 每一条边都有一个边权;
  3. 每一个非叶子节点指向其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)=2n1(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,d1,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=1k f(ni,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;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值