牛客练习赛60 C 操作集锦

20 篇文章 0 订阅

C 操作集锦

题意

由小写英文字母组成的长度为n的一个字符串,求不相同的长度为k的子串的数量对1e9+7取模。
1 ≤ n ≤ 1 e 3 0 ≤ k ≤ n 1≤n≤1e3 \quad 0 \leq k \leq n 1n1e30kn

思路

D P DP DP 正难则反
正难则反,我们求所有不同的子串,不如把所有子串的数量求出来再减掉所以相同的子串数量。
若不去重求所有长度为k的子串的数量,则可由 f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − 1 ] f[i][j]=f[i-1][j]+f[i-1][j-1] f[i][j]=f[i1][j]+f[i1][j1]推出到第 i i i个数为止长度为 j j j的子串数量。 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j]不难理解, f [ i − 1 ] [ j − 1 ] f[i-1][j-1] f[i1][j1]代表的是到第 i − 1 i-1 i1个数取了 j − 1 j-1 j1个数再加上第s[i]个数构成了 j j j个数。
本题的难点在于如何去重。我们观察一个字符串 x . . . a 1 . . . a 2 x...a_1...a_2 x...a1...a2
发现如果以 a a a结尾的长度 j j j的字符串会在 f [ p o s a [ 1 ] ] [ j ] = f [ p o s a 1 − 1 ] [ j ] + f [ p o s a [ 1 ] − 1 ] [ j − 1 ] f[pos_{a[1]}][j]=f[pos_{a1}-1][j]+f[pos_{a[1]}-1][j-1] f[posa[1]][j]=f[posa11][j]+f[posa[1]1][j1]中计算一次以 a 1 a_1 a1为结尾的子串,而 f [ p o s a [ 2 ] ] [ j ] = f [ p o s a 2 − 1 ] [ j ] + f [ p o s a [ 2 ] − 1 ] [ j − 1 ] f[pos_{a[2]}][j]=f[pos_{a2}-1][j]+f[pos_{a[2]}-1][j-1] f[posa[2]][j]=f[posa21][j]+f[posa[2]1][j1]中又会计算一次以 a 2 a_2 a2为结尾的子串,那么就重复计算了前面的那部分以 a 1 a1 a1为结尾的子串,显然后者包括前者且大于等于前者。
所以这里要用到 p r e [ s [ i ] ] pre[s[i]] pre[s[i]]来记录上一个 s [ i ] s[i] s[i]所在的位置,当 p r e [ s [ i ] ] > 0 pre[s[i]]>0 pre[s[i]]>0时,即上一个 s [ i ] s[i] s[i]存在时,及时的减去上一个以 f [ p r e [ s [ i ] ] − 1 ] [ j − 1 ] f[pre[s[i]]-1][j-1] f[pre[s[i]]1][j1]
坑点:注意k为0时答案为1(出题人说的),注意初始化。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
const double eps = 1e-8;
const int NINF = 0xc0c0c0c0;
const int INF  = 0x3f3f3f3f;
const ll  mod  = 1e9 + 7;
const ll  maxn = 1e6 + 5;
const int N = 1e3+5;

ll n,k,pre[N],f[N][N];
char s[N];

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin>>n>>k;
	cin>>s+1;
	f[0][0]=1;
	for(int i=1;i<=n;i++){
		f[i][0]=1;
		for(int j=1;j<=i;j++){
			f[i][j]=f[i-1][j]+f[i-1][j-1];
			if(pre[s[i]]) f[i][j]-=f[pre[s[i]]-1][j-1];
			f[i][j]%=mod;
		}
		pre[s[i]]=i;
	}
	cout<<(f[n][k]+mod)%mod<<'\n';
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值