一. 至少转最多
处理何种问题:有m 种类型的物品(每种类型的物品个数可视为无限),从中选出n个物品,求物品s至少有一次连续选k件的方案数。
例题:抛硬币,抛n次,求正面至少连续出现k次的种数。抛3次,连续出现2次正面的种数是3(110,011,111)。
性能:时间复杂度为O(nm),空间复杂度也是(nm)。
原理:设事件A={最多选n个s物品连续},B={最多选k-1个s物品连续},
A- B={至少选k件s件物品连续}。 dp[i][j] 表示第i次选择时选择j 物品时满足
条件的方案数,注意:dp[i][j]求的是最多选s个物品连续的情况;
l 对于非s物品,对整个没有影响,所以dp[i][~s]=sum[i-1];
l 对于s物品:
n 当i<=u 时(n,k-1都为u),dp[i][s]=sum[i-1](因为肯定没超过n)。
n 当i==u+1时,dp[i][s]=sum[i-1]-1(这种情况下,只有一种可能会超过n)。
n 当i>u+1时,dp[i][s]=sum[i-1]-dp[i-u-1][~s](此时要减去i前面已经出现连续u个s的情况,即从i-u到i-1这一段都是s的情况,其个数就是
dp[i-u-1][~s]之和,为什么没有减掉dp[i-u-1][s],因为当i-u-1为s时,所有i-1都肯定是~s了)唉,dp就是在人脑里跑递归啊。
实现步骤:至少转最多,用DP实现。
备注:此题涉及面比较广,可作为入门dp的练习题,且拓展性也不错,曾遇到一个至少+一个最多。写此题目的不是为了做模板用,只为加深dp理解。
输入样例解释:
10 3 2 5 //n m s k
输出样:
1053 //方案数
#include<iostream>
#include<cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
int n,m,s,k,ans;
const int mod=1e9+7;
long long sum[1000010];
long long dp[1000010][10];
long long f(int u)
{
for(int i=1; i<=n; ++i)
{
for(int j=0;j<m;++j)
{
if(j!=s)
dp[i][j]=sum[i-1];
}
if(i<=u)
dp[i][s]=sum[i-1];
else if(i==u+1)
dp[i][s]=sum[i-1]-1;
else if(i>u+1)
{
dp[i][s]=sum[i-1];
for(int j=0;j<m;++j)
{
if(j!=s)
dp[i][s]-=dp[i-u-1][j];
}
}
sum[i]=0;
for(int j=0;j<m;++j)
{
sum[i]+=dp[i][j];
}
}
return sum[n];
}
int main()
{
while(~scanf("%d%d%d%d",&n,&m,&s,&k))
{
sum[0]=1;
printf("%lld\n",f(n)-f(k-1));
}
return 0;
}
/*
//暴力验证代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,s,k,ans;
int arr[100];
void dfs(int ctor)
{
if(ctor>n)
{
int sum=0,flag=0;
for(int i=1;i<=n;++i)
{
if(arr[i]==s)
{
if(arr[i]!=arr[i-1])
sum=1;
else if(arr[i]==arr[i-1])
++sum;
}
else
sum=0;
if(sum>=k)
flag=1;
}
if(flag)
{
++ans;
printf("%d:",ans);
for(int i=1;i<=n;++i)
printf("%d",arr[i]);
printf("\n");
}
}
else
{
for(int i=0;i<m;++i)
{
arr[ctor]=i;
dfs(ctor+1);
}
}
}
int main()
{
while(~scanf("%d%d%d%d",&n,&m,&s,&k))
{
ans=0;
arr[0]=-1;
dfs(1);
cout<<ans<<endl;
}
return 0;
}
*/

浙公网安备 33010602011771号