ZOJ - 3747 Attack on Titans [有条件限制的经典递推计数好题]

题目大意是 有n个士兵的排列,士兵有GRP三种类型,要求至少有m个G士兵连续,至多有k个R士兵连续,问有多少种方案,答案 取模1e9+7

题目分析
我们看到这个题目,很显然是一个计数的问题,那么如果从简单入手考虑,假设不存在上述条件,就是有n个士兵,每个士兵有三种类型,很显然就是 3n 3 n 种情况,然后现在我们加上假设条件,根据尝试,我们发现至多有K个R士兵连续这种情况比较容易考虑,这是我们可以采用递推的思路,现在我用f(n)来表示到达第n个位置时满足上述限制条件的种类数,很显然,当n<=K时,限制条件不起作用,所以 f(n)=f(n1)3 f ( n ) = f ( n − 1 ) ∗ 3 ,当n=k+1时,只有一种情况:所有士兵全为R的时候的,是不满足条件的,此时我们只要删去这一种情况就可以了,即 f(n)=f(n1)31 f ( n ) = f ( n − 1 ) ∗ 3 − 1
当n逐渐增加的时候,由于前面已经不存在不满足条件的情况了,所以,我们只要考虑最后一个新增的为R时,是否会形成连续k+1个R,假设形成了,有多少种这样的情况呢,很显然就是 f(nk1) f ( n − k − 1 ) 种啦,即,当 n>k+1 n > k + 1 时, f(n)=f(n1)3f(nk1) f ( n ) = f ( n − 1 ) ∗ 3 − f ( n − k − 1 )
但是我们现在有两个限制条件,并且至少有m个连续并不是很容易计算,此时,我们就会想到正难则反,我们通过计算至多有n个连续的G减去至多有m-1个连续的G的情况(连续人数的范围就控制在m-n之间)就会是我们要的至少有m个G连续。
再根据上面的方法 就能计算G的可能性。
由于现在我们有两个限制条件,三种士兵,所以我们不再简单的乘三,把各种情况都分离开来。用一个dp数组来记录种类数,dp[i][j] ,i表示当前序列种的第i个士兵,j代表士兵的种类。
0表示G,1表示P,2表示R;
现在我们的问题变成了 {至多n个连续的G,k个连续的R的个数} {至多m-1个连续的G,k个连续的R的个数}
那么怎么计算 至多u个连续G,v个连续R的种类数呢?
iu时 显然 dp[i][0]=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2])%mod;
而当 i=u+1 i = u + 1 时, dp[i][0]=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2]-1)%mod;
i>u+1 i > u + 1
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+mod)%mod;
//因为在我们删去的情况中i-u到i-1这一段都成了G,所以第i-u-1个不可能G,所以我们只要减去第i-u-1个为P,R的两种情况就好了。
以此递推即可解决,对于dp数组的初始化,我们只要满足一开始能够计算出三个位置的值,所以在第0个无意义的位置, 给其中一个赋初值为1就好了

代码如下

#include <bits/stdc++.h>
#define cl(arr,val) memset(arr,val,sizeof(arr))
using namespace std;
typedef long long ll;
const int mod=1e9+7;
ll dp[1000005][4];
 int n,m,k;
ll solve(int u,int v)
{
    //cl(dp,0);
    dp[0][2]=dp[0][0]=0;
    dp[0][1]=1;
    for(int i=1; i<=n; i++)
    {
        dp[i][1]=(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+1)
        {
            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+mod)%mod;
        }
        if(i<=v)
        {
            dp[i][2]=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2])%mod;
        }
        else if(i==v+1)
        {
            dp[i][2]=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2]-1)%mod;
        }
        else
        {
          dp[i][2]=((dp[i-1][0]+dp[i-1][1]+dp[i-1][2]-dp[i-v-1][0]-dp[i-v-1][1])%mod+mod)%mod;
        }
    }
    return (dp[n][0]+dp[n][1]+dp[n][2])%mod;
}
int main()
{
    while(scanf("%d%d%d",&n,&m,&k)!=EOF)
    {
        printf("%lld\n",(solve(n,k)-solve(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、付费专栏及课程。

余额充值