[DP记录]ARC110E Shorten ABC
command_block
2021-10-27 08:04:17
**题意** : 给出长为 $n$ 的字符串 $S$ ,字符集为 $\{\texttt{A},\texttt{B},\texttt{C}\}$ 。
可执行下列操作任意次(包括零次):
- 选定 $1\leq i\leq |S|-1$ 使得 $S_i\neq S_{i+1}$ ,将 $S_i$ 换成和原来的 $S_i,S_{i+1}$ 均不同的字符(这个字符是确定的),并删除 $S_{i+1}$。
求你能获得的不同的 $S$ 的个数。
$n\leq 10^6$ ,时限$\texttt{2s}$。
------------
可以发现,最终一定是一段段的字符合并在了一起。
记 $\texttt{A},\texttt{B},\texttt{C}$ 的权值分别为 $1,2,3$ ,每次合并相当于将两者的权值 xor 。这告诉我们,区间确定了,合成出的字符就确定了。
但这没有考虑相邻不同的限制。
- 若区间异或和为 $0$ ,显然有异常。
- 若非 $0$ ,区间清一色是显然无法合成。
- 否则,必能找到一对能合成的字符,且合成之后仍然满足“区间异或和非0”,“区间并非清一色”,归纳即可。
具体地,随便找一对相邻不等字符 $a,b$,若合成后区间清一色,说明其余字符都为与 $a,b$ 不同的 $c$ ,那么可以让 $b,c$ 合并(谁不是边界用谁)。
综上,除非区间异或和为 $0$ 或清一色(称这样的区间是好的),总有合并方案。(单个字符也可以看做有合并方案)
现在就相当于,将 $S$ 划分为若干好段,且统计好段们的异或和序列数目。
思考如何判定一个 $T$ 能否被生成,考虑贪心(简化方案原则)。
首先 $T$ 的异或和要等于 $S$ 的异或和。
$T$ 可能有许多的生成方案,我们考虑其中最“靠前”的一个,即每个好段的右边界都尽量靠前。
每次选一个最短的可行的段进行转移,到 $T$ 的最后一个字符,直接选完 $S$ 剩下的所有字符。
为了说明正确性,我们需要证明对于任意存在方案的 $T$ ,必然可以被上述算法构造出一种方案。
对于 $T$ 的某种方案,选出第一个好段的一个异或和为 $0$ 的后缀,满足去除之后第一个好段仍然是好的,将其转接到第二个好段前方,如此重复,不难发现,正得到了最“靠前”的方案。(调整法)
于是我们只需要计算“靠前”的方案数目。
记 $f_i$ 为 $S[1:i]$ 的“靠前”的好段划分数目。
每次向前枚举上一个字符 $\texttt{A},\texttt{B},\texttt{C}$ ,找出最小段进行转移。
注意最后一段(必须是异或和为 $0$)要补掉,故答案不仅仅是 $f_n$。特殊地,当整个串清一色时不能补 $0$ ,此时答案必为 $1$ ,特判。
复杂度 $O(n)$。
```cpp
#include<algorithm>
#include<cstdio>
#define MaxN 1005000
using namespace std;
const int mod=1000000007;
int n,f[MaxN],nxt[MaxN][4],las[4];
char s[MaxN],o[MaxN];
int main()
{
scanf("%d%s",&n,s+1);
bool fl=1;
for (int i=1;i<n;i++)fl&=(s[i]==s[i+1]);
if (fl){puts("1");return 0;}
for (int i=1;i<=n;i++)
o[i]=o[i-1]^(s[i]=='A' ? 1 : s[i]=='B' ? 2 : 3);
for (int k=0;k<=3;k++)las[k]=n+1;
for (int i=n;i>=0;i--){
las[o[i]]=i;
for (int k=0;k<=3;k++)if (k!=o[i])
nxt[i][o[i]^k]=las[k];
nxt[i][o[i]^o[i+1]]=i+1;
}
f[0]=1;
for (int i=0;i<n;i++)
for (int k=1;k<=3;k++)
f[nxt[i][k]]=(f[nxt[i][k]]+f[i])%mod;
int ans=0;
for (int i=1;i<=n;i++)
if (o[i]==o[n])ans=(ans+f[i])%mod;
printf("%d",ans);
return 0;
}
```