Atcoder Grand Contest 027
027E ABBreviate
题目描述
解法
首先思考判定性问题,如何判断字符串 \(s\) 能否操作到 \(t\) ?
通过一些基本的分析可知,字符串 \(t\) 上的每个字符对应着 \(s\) 上的一段区间。所以我们先思考简化问题:如果判断一个字符串 \(s\) 是否能够操作成单个字符 \(c\) ?
从必要条件入手,首先寻找不变量,我们给 ab
分别加权 \(1/2\),操作不改变模 \(3\) 意义下的权值,所以两者的权值应该相等。此外还要保证初始时可以操作,也就是要求初始时存在相邻相等的字符。
发现证明充分性是很简单的,因为每个时刻我们都能保证有相邻相等的字符可以操作。那么我们得到结论:字符串 \(s\) 可以操作成单个字符 \(c\),当且仅当权值相等,并且满足 \(|s|=1\) 或者 \(s\) 存在相邻相等字符。
回到原来的判定性问题,考虑类似子序列自动机的贪心匹配思想,对于 \(t\) 的第一个字符 \(c\),我们考虑找到 \(s\) 中和它权值相等的最短前缀,然后删去 \(c\) 和 \(s\) 的最短前缀。不难发现这个前缀一定可以操作成 \(c\)(因为最短且权值相等)
问题是这样贪心匹配最后还会剩下一段,我们想要把这一段消去,考虑必要条件是这一段的权值为 \(0\),我们考虑把 \(t\) 的最后一个字符和这一段消去,如果不能消去那么一定是类似 a
和 baba
的形式。但是我们可以留下一个 a
,把 abab
传递到前面去,由于整个序列存在相邻相等字符(否则无法操作),所以说最后一定能被消掉。
那么得到结论,\(s\) 可以操作成 \(t\),当且仅当 \(s\) 可以被成功划分,并且最后一段的权值为 \(0\)
回到计数问题,可以利用上面的结论建出自动机然后计数。考虑终止状态就是所有后缀权值为 \(0\) 的点,转移就是最近可以成为 a/b
的一段,可以从后往前边构建边计数,时间复杂度 \(O(n)\)
总结
本质不同字符串的计数问题,在没有深入分析之前,你就应该明白方法可能是:类似子序列自动机的贪心匹配思想。
#include <cstdio>
#include <cstring>
const int M = 100005;
const int MOD = 1e9+7;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,nxt[M][2],dp[M];char s[M];
signed main()
{
scanf("%s",s+1);n=strlen(s+1);
int f=0,w=0;
for(int i=1;i<n;i++) f|=(s[i]==s[i+1]);
if(!f) {puts("1");return 0;}
dp[n+1]=1;
nxt[n+1][0]=nxt[n+1][1]=nxt[n+2][0]=nxt[n+2][1]=n+2;
for(int i=n;i>=1;i--)
{
w+=(s[i]=='a')?1:2;w%=3;
nxt[i][0]=s[i]=='a'?i+1:s[i+1]=='b'?i+2:nxt[i+2][0];
nxt[i][1]=s[i]=='b'?i+1:s[i+1]=='a'?i+2:nxt[i+2][1];
dp[i]=(dp[nxt[i][0]]+dp[nxt[i][1]]+(w==0))%MOD;
}
printf("%d\n",(dp[1]+MOD-(w==0))%MOD);
}