题目描述
输入描述:
输出描述:
示例1
输入
dbca
输出
10
示例2
输入
dbcad
输出
15
题目大意
给定一个字符串
。并定义操作
表示对于字符串
,从
到
区间内的每个字符都改为当前位置到
的最大值。
比如,有字符串
,经过
的操作后,变成
。而如果经过
操作后,则变成
,没有变。
现在对于串
,求
有多少种可能。其中,
。
分析
首先把题目翻译成人话:
对于
,要求
的一段区间变成单调上升,并在这个区间里再选定一段子串变成单调上升,求变化后有多少不同的子串。
首先很容易想到,因为一开始已经在 中选取了一段变成单调上升,那么在这个区间里再进行操作是无意义的。因此 的套娃可以去掉一层,实际上就是一个普通的 ,然后要求有多少个不同的子串。
其次考虑继续简化。想:如果 移动,那么整体的子串影响是比较大的,而如果移动 ,那么只会增加一个字符进来,而不会影响到已经有的字符。因此,我们考虑固定 ,将 移动,可以发现,对于任意的 ,其形成子串的个数就是 可以取的个数,也就是 后面字符的个数。于是考虑固定 在 的位置,然后求不同的后缀,然后对于每个不同的后缀,都去乘上长度即可。
到此,题目被简化成了对于字符串 ,所有 ,求所有 有多少不同的子串。
思路一
用广义后缀自动机,这是各大高校所采取的方法,是省选的内容,作为备战提高 ,根本不会,因此就是扯一点水一下 的长度[doge]。
思路二
听 的话( ),用了 走天下( )。
考虑Hash。首先对于每一个后缀,由于它是单调上升的,又只有10个字母,因此它总可以表示成
,转化一下即可以表示成
。
比如,串
可以表示成
。
此时我们如果考虑从
开始
结尾的子串1,那么
有
种,
有
种,运用组合数学(其实不用,乘法原理),可知有
种可能。是不是很简单地解决了后缀个数的问题。
但是事情远没有想象的那么简单,如果有串 以及 ,那么在统计的时候就会有重复。而且还可能不止这样,有许多种也是有可能的。
因此必须想个办法解决一下。由于这些串的中间部分是相同的,我们可以直接考虑首尾
和
的个数,存在一个
里,如下:
如果有这样4个重复的子串,要求它们的不同子串的个数。看上去很花,排序:
现在已经根据
的多少降序排列,接下来我们一个一个考虑:
对于第一个,有
种情况;
对于第二个,有异于上述的
种;
对于第三个,有异于上述的
种;
对于第四个,有异于上述的
种;
因此总共有
种。2
回顾刚刚的策略,我们对
的数量取当前的
,然后进行乘积求和。
那么具体的策略看官可以自己多试几组,以下给出公式:
有点类似容斥的感觉。
至于你问不同后缀怎么搞,以及后缀时
移动造成的影响怎么弄……诶,不要问,问就是
。
至于你问怎么判断中间是相同的……诶,这还真有讲究,虽然思路二一开头就点出了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
这是我写得最累的一篇题解了……