2020牛客暑期多校训练营Count New String(Hash,动态规划,枚举,组合统计)

Count New String

题目描述

在这里插入图片描述

输入描述:

在这里插入图片描述

输出描述:

在这里插入图片描述

示例1

输入

dbca

输出

10

示例2

输入

dbcad

输出

15

题目大意

给定一个字符串 S S S。并定义操作 f ( S , x , y ) f(S,x,y) f(S,x,y)表示对于字符串 S S S,从 x x x y y y区间内的每个字符都改为当前位置到 x x x的最大值。
比如,有字符串 a b a c a a d abacaad abacaad,经过 f ( S , 1 , 7 ) f(S,1,7) f(S,1,7)的操作后,变成 a b b c c c d abbcccd abbcccd。而如果经过 f ( S , 5 , 7 ) f(S,5,7) f(S,5,7)操作后,则变成 a b a c a a d abacaad abacaad,没有变。
现在对于串 S S S,求 f ( f ( S , x 1 , y 1 ) , x 2 − x 1 + 1 , y 2 − x 1 + 1 ) f(f(S,x_1,y_1),x_2-x_1+1,y_2-x_1+1) f(f(S,x1,y1),x2x1+1,y2x1+1)有多少种可能。其中, 0 ≤ x 1 ≤ x 2 ≤ y 2 ≤ y 1 ≤ n 0\le x_1\le x_2\le y_2\le y_1\le n 0x1x2y2y1n

分析

首先把题目翻译成人话
对于 S S S,要求 S S S的一段区间变成单调上升,并在这个区间里再选定一段子串变成单调上升,求变化后有多少不同的子串。

首先很容易想到,因为一开始已经在 S S S中选取了一段变成单调上升,那么在这个区间里再进行操作是无意义的。因此 f ( f ( S , x 1 , y 1 ) , x 2 − x 1 + 1 , y 2 − x 1 + 1 ) f(f(S,x_1,y_1),x_2-x_1+1,y_2-x_1+1) f(f(S,x1,y1),x2x1+1,y2x1+1)的套娃可以去掉一层,实际上就是一个普通的 f ( S , x , y ) f(S,x,y) f(S,x,y),然后要求有多少个不同的子串。

其次考虑继续简化。想:如果 x x x移动,那么整体的子串影响是比较大的,而如果移动 y y y,那么只会增加一个字符进来,而不会影响到已经有的字符。因此,我们考虑固定 x x x,将 y y y移动,可以发现,对于任意的 x x x,其形成子串的个数就是 y y y可以取的个数,也就是 x x x后面字符的个数。于是考虑固定 y y y n n n的位置,然后求不同的后缀,然后对于每个不同的后缀,都去乘上长度即可。

到此,题目被简化成了对于字符串 S S S,所有 i ∈ [ 1 , n ] i\in[1,n] i[1,n],求所有 f ( S , i , n ) f(S,i,n) f(S,i,n)有多少不同的子串。

思路一

用广义后缀自动机,这是各大高校所采取的方法,是省选的内容,作为备战提高 O I e r OIer OIer,根本不会,因此就是扯一点水一下 p a p e r paper paper的长度[doge]。

思路二

x i n j u n xinjun xinjun的话( h u a ˋ hu\mathbf{\grave{a}} huaˋ),用了 H A S H HASH HASH走天下( x i a ˋ xi\mathbf{\grave{a}} xiaˋ)。

考虑 H a s h Hash Hash。首先对于每一个后缀,由于它是单调上升的,又只有 10 10 10个字母,因此它总可以表示成 a a . . . a   b b . . . b   c c . . . c   . . .   j j . . . j aa...a \,bb...b \,cc...c \,... \,jj...j aa...abb...bcc...c...jj...j,转化一下即可以表示成 n 1 a , n 2 b , … , n 10 j n_1a,n_2b,\dots,n_{10}j n1a,n2b,,n10j
比如,串 a a a b b b c d d d d aaabbbcdddd aaabbbcdddd可以表示成 3 a , 3 b , 1 c , 4 d 3a,3b,1c,4d 3a,3b,1c,4d
此时我们如果考虑从 a a a开始 d d d结尾的子串1,那么 a a a 3 3 3种, d d d 4 4 4种,运用组合数学(其实不用,乘法原理),可知有 3 ∗ 4 = 12 3*4=12 34=12种可能。是不是很简单地解决了后缀个数的问题。

但是事情远没有想象的那么简单,如果有串 3 a , 3 b , 1 c , 4 d 3a,3b,1c,4d 3a,3b,1c,4d以及 2 a , 3 b , 1 c , 2 d 2a,3b,1c,2d 2a,3b,1c,2d,那么在统计的时候就会有重复。而且还可能不止这样,有许多种也是有可能的。

因此必须想个办法解决一下。由于这些串的中间部分是相同的,我们可以直接考虑首尾 a a a d d d的个数,存在一个 p a i r pair pair里,如下:
( 3 , 4 ) ( 2 , 2 ) ( 1 , 3 ) ( 4 , 1 ) (3,4)\qquad(2,2)\qquad(1,3)\qquad(4,1) (3,4)(2,2)(1,3)(4,1)
如果有这样4个重复的子串,要求它们的不同子串的个数。看上去很花,排序:
( 4 , 1 ) ( 3 , 4 ) ( 2 , 2 ) ( 1 , 3 ) (4,1)\qquad(3,4)\qquad(2,2)\qquad(1,3) (4,1)(3,4)(2,2)(1,3)
现在已经根据 a a a的多少降序排列,接下来我们一个一个考虑:
对于第一个,有 4 4 4种情况;
对于第二个,有异于上述的 9 9 9种;
对于第三个,有异于上述的 0 0 0种;
对于第四个,有异于上述的 0 0 0种;
因此总共有 4 + 9 = 13 4+9=13 4+9=13种。2

回顾刚刚的统计,计算机是不会像人一样考虑的,如果每次都判一遍每种取法的可行性的话, T T T了呀。所以我们从排好序的 a a a的个数入手。

对于上面举的例子,可以发现,有 4 4 4 a a a的首部的子串是只在 ( 4 , 1 ) (4,1) (4,1)时才会实现,到了 ( 3 , 4 ) (3,4) (3,4)以至更后面更不可能得到 4 4 4 a a a,因此我们可以说, ( 4 , 1 ) (4,1) (4,1)贡献了 1 1 1个串,这个串是由 4 4 4 a a a开头的。其他的串在后面会出现。
那么尾部要取多少呢?要取历史最优值。因为对于任意的 a a a的长度,由于它是降序的,所以之前肯定是可以取到同样长度的 a a a的,而之前只是算了唯一贡献,而没有算之后可能会重复的贡献,因此可以认为 d d d可以取到历史最优。

分析后得出,我们只要考虑当前有哪些是之后肯定取不到的,将其加入答案里即可。现设首部长度 f i r s t 1... n first_{1...n} first1...n和尾部长度 s e c o n d 1... n second_{1...n} second1...n,有统计公式(前面已经排序):
f o r ( i n t   i = 1 ; i ≤ n ; i + + ) for(int\,i=1;i\le n;i++) for(inti=1;in;i++)
m a x n = m a x ( m a x n , s e c o n d i ) , \qquad maxn=max(maxn,second_i), maxn=max(maxn,secondi),
a n s + = m a x n ∗ ( f i r s t i − f i r s t i + 1 ) ; \qquad ans+=maxn*(first_i-first_{i+1}); ans+=maxn(firstifirsti+1);3
当然,如果是最后了,要把剩下所有的都统计。

至于你问不同后缀怎么搞,以及后缀时 x x x移动造成的影响怎么弄……诶,不要问,问就是 d p dp dp
至于你问怎么判断中间是相同的……诶,这还真有讲究,虽然思路二一开头就点出了 H a s h Hash Hash,但是好像分析到现在还没用到。是的就是用 H a s h Hash Hash,如果对 H a s h Hash Hash还不是很了解的看官,可以看看我的这篇博客,比较详细的简绍了一下,当然更好是上网搜 H a s h Hash Hash的专题。我们把中间的字符求下 H a s h Hash Hash然后就扔进 m a p map map里,嗯这就方便了嘛!

至此,我们已经完全分析了整道题,真是深藏不露,蒟蒻不敢做啊~
如还有疑问,请看代码。

代码

#include<bits/stdc++.h>
#define ll long long
#define inf 1<<30
using namespace std;
const int MAXN=1e5+10;
const int MOD=1e9+7;
vector<pair<int,int> > vec;
map<int,vector<pair<int,int> > > mp;//从未开过如此厚颜无耻的map
char s[MAXN];
int len,dp[MAXN][11];//dp[i][j]表示后缀第i位为止字符j出现了dp[i][j]次,0是‘a’
int main()
{
    scanf("%s",s);len=strlen(s);
    for(int i=len-1;i>=0;i--){
        int x=s[i]-'a';dp[i][x]++;
        for(int j=0;j<=x;j++) dp[i][x]+=dp[i+1][j];
        for(int j=x+1;j<10;j++) dp[i][j]=dp[i+1][j];
    }//dp转移
    ll ans=0;
    for(int i=0;i<10;i++)
        for(int j=i;j<10;j++){//枚举首尾的两个字符
            mp.clear();
            for(int k=0;k<len;k++)
                if(dp[k][i]&&dp[k][j]){
                    int hash=0;
                    for(int l=i+1;l<j;l++)
                        hash=(1ll*hash*233+dp[k][l])%MOD;//什么?还有人不知道233做进制?不会吧不会吧?
                    mp[hash].push_back(make_pair(dp[k][i],dp[k][j]));//扔进map
                }//求出Hash
            map<int,vector<pair<int,int> > >::iterator it=mp.begin();//迭代器
            for(it;it!=mp.end();it++){
                vec=it->second;
                sort(vec.begin(),vec.end());//回顾上面我们做的过程,先排序
                if(i==j){
                    ans+=vec[vec.size()-1].first;continue;
                }//如果是同个字符
                int maxn=0;
                for(int k=vec.size()-1;k>=0;k--){
                    maxn=max(maxn,vec[k].second);
                    if(k) ans+=1ll*(vec[k].first-vec[k-1].first)*maxn;
                    else ans+=1ll*vec[k].first*maxn;
                }//回顾上面给出的公式
            }
        }
    printf("%lld\n",ans);
}

END

这是我写得最累的一篇题解了……


  1. 文中的子串指已经经过操作的单调上升的子串。 ↩︎

  2. 这里可能笔者有误,欢迎下方评论指正。 ↩︎

  3. 这里是顺序,程序中给出的是倒序处理(sort懒得写cmp和重置了) ↩︎

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值