【枚举区间思想+DP】子串的子序列

港控/mmm° 2024-03-16 20:02 134阅读 0赞

F-子串的子序列_牛客小白月赛62 (nowcoder.com)

题意:

45d4a87408594a30ac7cc222f3ce00e6.png

3c9bb7b619bb44e288c936db0b34db45.png

思路:

复盘一下应该有的思路:

首先n^2枚举肯定超时,我们枚举的是一个区间

枚举区间有一些trick:

1.枚举其中一个右(左)端点,O(1)或O(logn)计算满足条件的左(右)端点个数 ,可以组合数学 or DP 计算

2.单独计算每个元素的贡献,O(1)或O(logn)计算合法区间的左端点和右端点匹配个数,这种一般是乘法原理

在这里我们用到的是第一种思想

我们去枚举右端点r,然后计算满足条件的左端点的个数

我就想到这里,甚至连DP都没想到

这里的DP并不是传统意义的子序列DP,也就是说,并不是枚举两个指针然后转移(LIS),也不是统计前缀满足某个性质的子序列的最值(接龙数列 or 绝世好题),这里用的是状态机DP

设dp[i][0/1][0/1]表示,前i个字符,a的数量的奇偶性是0/1,ac的数量的奇偶性是0/1的子串个数

为什么是这样设计的,其实一开始想的应该只是ac的数量的奇偶性,但是发现这样不能算贡献,考虑多一个字符c,贡献还与前缀a的数量的奇偶性有关,所以应该还有加上一维

这里告诉我们,在考虑计算贡献的DP时,考虑多一格,然后思考如何计算贡献

然后转移的时候注意这是子串数量,所以应该从i-1转移

(看代码)为什么一开始要++,因为考虑新加的字符自成一个区间

还有一个坑点:

然后统计答案的时候会把ac子序列个数为0的区间个数算进去,因此我们需要减去这些区间

这个怎么做?同样也是那个枚举区间的trick,我们去枚举左区间,右侧ac的c左边那格-i+1就是区间个数了,统计一下即可

Code:

  1. #include <bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. using i64 = long long;
  5. const int mxn=5e5+10;
  6. const int mxe=5e5+10;
  7. const int mod=1e9+7;
  8. string s;
  9. int N;
  10. int pre_a[mxn],pre_c[mxn];
  11. int dp[mxn][2][2];
  12. int no_ac(){
  13. int res=0;
  14. for(int i=1;i<=N;i++){
  15. if(s[i]=='a'){
  16. pre_a[i]=i;
  17. pre_c[i]=pre_c[i-1];
  18. }else if(s[i]=='c'){
  19. pre_c[i]=i;
  20. pre_a[i]=pre_a[i-1];
  21. }else{
  22. pre_a[i]=pre_a[i-1];
  23. pre_c[i]=pre_c[i-1];
  24. }
  25. }
  26. for(int r=1;r<=N;r++) res+=r-(pre_a[pre_c[r]]+1)+1;
  27. return res;
  28. }
  29. void solve(){
  30. cin>>s;
  31. N=s.size();
  32. s=" "+s;
  33. int ans=0;
  34. for(int i=1;i<=N;i++){
  35. if(s[i]=='a'){
  36. dp[i][1][0]++;
  37. dp[i][1][1]+=dp[i-1][0][1];
  38. dp[i][1][0]+=dp[i-1][0][0];
  39. dp[i][0][1]+=dp[i-1][1][1];
  40. dp[i][0][0]+=dp[i-1][1][0];
  41. }else if(s[i]=='c'){
  42. dp[i][0][0]++;
  43. dp[i][1][1]+=dp[i-1][1][0];
  44. dp[i][1][0]+=dp[i-1][1][1];
  45. dp[i][0][1]+=dp[i-1][0][1];
  46. dp[i][0][0]+=dp[i-1][0][0];
  47. }else{
  48. dp[i][0][0]++;
  49. for(int j=0;j<2;j++){
  50. for(int k=0;k<2;k++){
  51. dp[i][j][k]+=dp[i-1][j][k];
  52. }
  53. }
  54. }
  55. ans+=dp[i][1][0]+dp[i][0][0];
  56. }
  57. cout<<ans-no_ac()<<'\n';
  58. }
  59. signed main(){
  60. ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
  61. int __=1;//cin>>__;
  62. while(__--)solve();return 0;
  63. }

发表评论

表情:
评论列表 (有 0 条评论,134人围观)

还没有评论,来说两句吧...

相关阅读

    相关 516 最长回文序列区间dp

    1. 问题描述: 给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

    相关 647 回文

    1. 问题描述: 给定一个字符串,你的任务是计算这个字符串中有多少个回文子串。具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。 示例 1:

    相关 序列

    这个可以是自己无聊点出来的,键盘输入流输入 一个字符串然后,给出所有子序列的集合,包括空和自己。虽然无用,但胜过于无。因为用了Math的随机数,时间成本可能会有点高。。。。。。