ZOJ 3747 Attack on Titans【带限制条件的递推dp+计数技巧】

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;
}





  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值