题目描述
输入描述:
输出描述:
示例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),x2−x1+1,y2−x1+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
0≤x1≤x2≤y2≤y1≤n。
分析
首先把题目翻译成人话:
对于
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),x2−x1+1,y2−x1+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
3∗4=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;i≤n;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∗(firsti−firsti+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
这是我写得最累的一篇题解了……