您的位置:首页 > 其它

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

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

题目分析

我们看到这个题目,很显然是一个计数的问题,那么如果从简单入手考虑,假设不存在上述条件,就是有n个士兵,每个士兵有三种类型,很显然就是3n3n种情况,然后现在我们加上假设条件,根据尝试,我们发现至多有K个R士兵连续这种情况比较容易考虑,这是我们可以采用递推的思路,现在我用f(n)来表示到达第n个位置时满足上述限制条件的种类数,很显然,当n<=K时,限制条件不起作用,所以 f(n)=f(n−1)∗3f(n)=f(n−1)∗3,当n=k+1时,只有一种情况:所有士兵全为R的时候的,是不满足条件的,此时我们只要删去这一种情况就可以了,即f(n)=f(n−1)∗3−1f(n)=f(n−1)∗3−1。

当n逐渐增加的时候,由于前面已经不存在不满足条件的情况了,所以,我们只要考虑最后一个新增的为R时,是否会形成连续k+1个R,假设形成了,有多少种这样的情况呢,很显然就是 f(n−k−1)f(n−k−1)种啦,即,当n>k+1n>k+1时,f(n)=f(n−1)∗3−f(n−k−1)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的种类数呢?

当i≤ui≤u时 显然 dp[i][0]=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2])%mod;

而当i=u+1i=u+1时, dp[i][0]=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2]-1)%mod;

i>u+1i>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
[0]+dp
[1]+dp
[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;

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  递推 计数