处理何种问题:有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 ",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(" "); } } 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; } */