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

Count New String

题目描述

在这里插入图片描述

输入描述:

在这里插入图片描述

输出描述:

在这里插入图片描述

示例1

输入

dbca

输出

10

示例2

输入

dbcad

输出

15

题目大意

给定一个字符串 S S 。并定义操作 f ( S , x , y ) f(S,x,y) 表示对于字符串 S S ,从 x x y y 区间内的每个字符都改为当前位置到 x x 的最大值。
比如,有字符串 a b a c a a d abacaad ,经过 f ( S , 1 , 7 ) f(S,1,7) 的操作后,变成 a b b c c c d abbcccd 。而如果经过 f ( S , 5 , 7 ) f(S,5,7) 操作后,则变成 a b a c a a d abacaad ,没有变。
现在对于串 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) 有多少种可能。其中, 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

分析

首先把题目翻译成人话
对于 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 ( S , x , y ) f(S,x,y) ,然后要求有多少个不同的子串。

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

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

思路一

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

思路二

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

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

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

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

回顾刚刚的策略,我们对 d d 的数量取当前的 m a x max ,然后进行乘积求和。
那么具体的策略看官可以自己多试几组,以下给出公式:
a n s + = m a x ( p a i r i . f i r s t p a i r i + 1 . f i r s t ) ans+=max*(pair_i.first-pair_{i+1}.first)
有点类似容斥的感觉。

至于你问不同后缀怎么搞,以及后缀时 x x 移动造成的影响怎么弄……诶,不要问,问就是 d p dp
至于你问怎么判断中间是相同的……诶,这还真有讲究,虽然思路二一开头就点出了Hash,但是好像分析到现在还没用到。是的就是用Hash,如果对Hash还不是很了解的看官,可以看看我的这篇博客,比较详细的简绍了一下,当然更好是上网搜Hash的专题。我们把中间的字符求下Hash然后就扔进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. 这里可能笔者有误,欢迎下方评论指正。 ↩︎

猜你喜欢

转载自blog.csdn.net/zhangchizc/article/details/107522473