update:添加了个优化
又是一道简单dp,qwq
我们读题,发现k和m的范围都很小,所以我们考虑从这里入手
我们设dp[i][j]表示选了i个数,最后一个是j的方案数,那么就有:
那么,我们想下转移。
首先,我们枚举新增了一个数a[i]
那么,分为两种情况:
一.a[i]被剔除了
这个情况下,相当于我们没有新增a[i],所以所有的dp的值都不变,不做任何操作即可
二.a[i]没被剔除
那么,我们按方程,难到要枚举前面选了几个数嘛?
不,我们直接枚举前面剔除多少个数,这样,如果我们枚举出前面剔除了几个数后,我们就知道现在序列中有几个数了。
然后,我们再枚举序列末尾是哪个数即可
但是,如果我们这么写转移:
连样例都过不了
为什么?
因为,我们其实之前的时候可能已经算了长度为i,末尾为a[i]的序列个数了,但,我们现在又新增了一个a[i]后,就可能将某些序列重复计算了
举个例子:
1 2 3 3
我们在i=3的时候,算了一遍1 2 3这个序列了
但i=4时,我们如果向上面那么转移的话,就会再算一遍1 2 3这个序列,只是结尾的3来自不同的a[i]而已。
那么,怎么办呢?
很简单,既然我们会重复计算,我们不加就好了!
因为,我们现在的长度为i-j,以a[i]结尾的序列中,一定包含了以前出现过的,长度为i-j,以a[i]结尾的序列,所以,我们直接将转移方程改成
当然,为了防止后效性,我们的j要从小到大枚举。
另外,当i<=j时,我们就要停止计算了(不然i-j-1就小于0了)
但是,这样的话,当i=j的时候,我们的值就会漏算,而漏算了这个后就会导致i-j=1的时候答案为0
怎么办呢?当i-j=1的时候,明显只有一个方案,所以,我们在每次算完后再令dp[1][a[i]]=1即可
复杂度:
Ps:我这么设方程相当于滚动了一维数组,如果不想想那么多,可以直接设方程:
表示前i个数中,剔除了j个,最后是k的序列的方案数,直接类似的转移即可
代码:
#include<bits/stdc++.h> using namespace std; const int N=1e5+1,mod=1e9+7; int a[N]; int dp[N][11]; int main(){ int n,m,k; while(~scanf("%d%d%d",&n,&m,&k)){ for(int i=1;i<=n;++i){ scanf("%d",&a[i]); } memset(dp,0,sizeof(dp));//清零 dp[1][a[1]]=1; for(int i=2;i<=n;++i){ for(int j=0;j<=m;++j){ if(i==j){ break; } int res=0; for(int p=1;p<=k;++p){ res=(res+dp[i-j-1][p])%mod; } dp[i-j][a[i]]=res; } dp[1][a[i]]=1; } int ans=0; for(int i=1;i<=k;++i){ ans=(ans+dp[n-m][i])%mod; } printf("%d\n",ans); } return 0; }
新增:
看了下转移方程,然后发现,后面这部分,可以使用前缀和来优化一下,所以,我们可以直接令,那么,转移就直接变成了
复杂度:[但是好像跑得更慢了???人丑自带大常数qwq]
代码:
#include<bits/stdc++.h> using namespace std; #define int long long const int N=1e5+1,mod=1e9+7; int a[N]; int dp[N][11]; int s[N]; signed main(){ int n,m,k; while(~scanf("%lld%lld%lld",&n,&m,&k)){ for(int i=1;i<=n;++i){ scanf("%lld",&a[i]); } memset(dp,0,sizeof(dp)); memset(s,0,sizeof(s)); dp[1][a[1]]=s[1]=1; for(int i=2;i<=n;++i){ for(int j=0;j<=m;++j){ if(i==j){ break; } s[i-j]-=dp[i-j][a[i]]; dp[i-j][a[i]]=s[i-j-1]; s[i-j]+=dp[i-j][a[i]]; s[i-j]%=mod; } s[1]+=(dp[1][a[i]]==0); dp[1][a[i]]=1; } s[n-m]=(s[n-m]+mod)%mod; printf("%lld\n",s[n-m]); } return 0; }