source:
题意:进击的巨人的题目背景(简直自带画面感和音效orz...),说要组建Levi班,现在从三个兵团中欲选出共n个人,对他们进行编号,编号有限制条件:驻扎兵团中至少要有m个分配到连续的编号,调查兵团中至多有k个分配到连续的编号,宪兵团不做限制,且三大兵团的人员近似无限。求一共有多少种编号方式?
思路:其实最开始我关于限制条件理解错了,以上的题意应该才是正确的理解,那么容易想到这应该是一个递推dp,但是关键是在于对于至少m个的处理!根据这题我学习了:将至少化为至多,然后减一下即可,也即:
驻扎兵团中至少要有m个分配到连续的编号 = 至多有n个分配到连续的编号的方案数 - 至多有m-1个分配到连续的编号
根据以上的转化就比较容易递推了,递推公式如下:
驻扎兵团(记为0号):dp[i][0]意为第i号位驻扎兵团时,满足限制条件的方案数(驻扎最多u个,调查最多v个)
1、当i<u时:dp[i][0] = dp[i-1][0]+dp[i-1][1]+dp[i-1][2];
2、当i=u时:dp[i][0] = dp[i-1][0]+dp[i-1][1]+dp[i-1][2] -1; //减1是因为只需排出一种即0号到u-1号都为驻扎的情况
3、当i>u时:dp[i][0] = dp[i-1][0]+dp[i-1][1]+dp[i-1][2]-dp[i-u-1][1]-dp[i-u-1][2]; //减去从i-u到i-1都为驻扎的情况
调查兵团(记为1号):dp[i][1]意为第i号位调查兵团时,满足限制条件的方案数(驻扎最多u个,调查最多v个)
1、当i<v时:dp[i][1] = dp[i-1][0]+dp[i-1][1]+dp[i-1][2];
2、当i=v时:dp[i][1] = dp[i-1][0]+dp[i-1][1]+dp[i-1][2] -1; //减1是因为只需排出一种即0号到u-1号都为调查的情况
3、当i>v时:dp[i][1] = dp[i-1][0]+dp[i-1][1]+dp[i-1][2]-dp[i-v-1][0]-dp[i-v-1][2]; //减去从i-v到i-1都为调查的情况
宪兵团(记为2号): dp[i][2]意为第i号位宪兵团时,满足限制条件的方案数(驻扎最多u个,调查最多v个)
dp[i][2] = dp[i-1][0]+dp[i-1][1]+dp[i-1][2];
注意:因为要取模,所以涉及到减法运算时注意减完要加模再取模!
代码如下:
#include<stdio.h>
#define MOD 1000000007
long long dp[1000005][3];
int n,m,k;
long long recursion(int u,int v) //带限制条件的递推:G至多为u个连续,R至多为v个连续
{
if(u>0) dp[0][0]=1; else dp[0][0]=0;
if(v>0) dp[0][1]=1; else dp[0][1]=0;
dp[0][2]=1;
for(int i=1;i<n;i++)
{
dp[i][2]=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2])%MOD;
if(i<u) dp[i][0]=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2])%MOD;
else if(i==u) dp[i][0]=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2]-1)%MOD;
else dp[i][0]=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2]-dp[i-u-1][1]-dp[i-u-1][2])%MOD;
if(i<v) dp[i][1]=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2])%MOD;
else if(i==v) dp[i][1]=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2]-1)%MOD;
else dp[i][1]=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2]-dp[i-v-1][0]-dp[i-v-1][2])%MOD;
}
return (dp[n-1][0]+dp[n-1][1]+dp[n-1][2])%MOD;
}
int main()
{
while(scanf("%d%d%d",&n,&m,&k)!=EOF)
{
printf("%d\n",((recursion(n,k)-recursion(m-1,k))%MOD+MOD)%MOD); //减法取模一定要注意!!!可能出现负数的情况!!!
}
return 0;
}