操作集锦
题目描述
有一款自走棋有26种操作,每种操作我们都用a,b,c,d,…,x,y,z的符号来代替.
现在牛牛有一个长度为n的操作序列,他现在可以从里面拿出某些操作来组合成一个操作视频, 比如说操作序列是abcdabcd,那么操作视频就有a,b,c,d,ab,ac,ad等(也就是操作序列的子序列).他现在想知道长度为k且本质不同的操作视频有多少种.
比如对于abab,长度为2且本质不同的结果有ab,aa,ba,bb。
考虑到答案可能非常大,你只需要输出在模1e9+7意义下的答案就可以了.
输入描述:
第一行两个整数n,k.
第二行一个长度为n的字符串,保证只存在小写字母.
输出描述:
一行一个整数表示长度为k且本质不同的操作视频的个数.
求子序列个数,明显要用到dp,而且可以显然推出dp方程:
dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
问题是还有重复的,怎么去重呢?
可以记录每个字母最后一次出现的位置,这个方程类似于前缀和,所以只要减去当前字母的前一次最后出现位置的值就行;
dp[i][j] -=dp[ pre[s[i]-‘a’]-1 ][ j-1];
扫描二维码关注公众号,回复:
10549497 查看本文章
这里要注意长度从0开始;
代码:
#include<bits/stdc++.h>
#define LL long long
#define pa pair<int,int>
#define ls k<<1
#define rs k<<1|1
#define inf 0x3f3f3f3f
using namespace std;
const int N=100010;
const int M=2000100;
const LL mod=1e9+7;
LL dp[1100][1100];
char s[1100];
int pre[30];
int main(){
int n,k;
cin>>n>>k;
scanf("%s",s+1);
dp[0][0]=1;
for(int i=1;i<=n;i++){//位置
for(int j=0;j<=i;j++){//长度
dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
if(pre[s[i]-'a']) dp[i][j]-=dp[pre[s[i]-'a']-1][j-1];
dp[i][j]%=mod;
}
pre[s[i]-'a']=i;
}
if(k==0){
cout<<1<<endl;
return 0;
}
if(dp[n][k]<0) dp[n][k]+=mod;
cout<<dp[n][k]<<endl;
return 0;
}