arc119f 题解
自动机写法。开始在做的时候题解没讲每个节点代表什么状态,自己推了一遍,记录一下。
思路
计数,求有多少种替换方式使得 \(0\) 到 \(n\) 存在一条长度小于等于 \(K\) 的路径。
可以做 \(O(n^3)\) 的 dp。设 \(dp_{i,a,b}\) 表示前 \(i\) 个位置,最近的 \(A\) 和 \(B\) 分别在 \(a\) 和 \(b\)。官方题解是优化 \(a\) 和 \(b\),发现有意义状态下 \(a\) 和 \(b\) 的差不超过一定数值。
观察发现,在 \(\text{BAA...AAB}\) 处,肯定选择从 \(B\) 直接跳过去。具体的,当有连续的 \(4\) 个 \(A\) 后,会选择直接从两端的 \(B\) 越过。所以有效的状态并不多。
考虑建一个自动机来求出任意状态对应的下一个状态。由于 \(A\) 和 \(B\) 可以取反得到,先只考虑当前在 \(A\),在 \(B\) 同理。以下几种有用状态。
-
状态 \(0\)。当前在 \(A\),上一个位置为 \(B\),记为 \(\text{A}\)。
-
状态 \(1\) 即状态 \(0\) 反过来,记为 \(\text{B}\)。
-
状态 \(2\)。当前在 \(A\),上一个位置为 \(B\),下一个位置为 \(A\),记为 \(\text{AA}\)。
-
状态 \(4\)。当前在 \(A\),上一个位置为 \(B\),下两个位置为 \(AA\),记为 \(\text{AAA}\)。
-
状态 \(6\)。当前在 \(A\),上一个位置为 \(B\),下三个位置为 \(AAA\),记为 \(\text{AAAA}\)。此时如果往后有 \(B\),直接先后退越过;否则走向自己,不会有 \(5\) 个 \(A\) 的状态。注意到此时无论如何都会后退,所以稍微调整一下。定义状态 \(6\) 为当前在 \(B\),接下来有连续 \(A\) 且一定会选择跳过。
-
状态 \(8\)。当前在 \(A\),上一个位置不重要,下一个位置为 \(B\),记为 \(\text{AB}\)。此时可以跳到下一个 \(A\),也可以爬到 \(B\),但不知道走一步后要去 \(A\) 还是 \(B\)。
-
状态 \(10\) 至 \(12\)。当前既可以当作在 \(A\),也可以当作在 \(B\),记为 \(\text{O}\),称为状态 \(12\)。状态 \(10\) 表示当前在 \(O\),下一个为 \(A\),记为 \(\text{OA}\)。
综上,有 \(13\) 种有用状态。
-
\(\text{A}\),\(\text{AA}\),\(\text{AAA}\),\(\text{AAAA}\),\(\text{AB}\)。
-
上面 \(5\) 种状态取反得到当前在 \(B\) 的另外 \(5\) 种。
-
\(\text{OA}\),\(\text{OB}\),\(\text{O}\)。
考虑转移。记 \(tree_{i,0/1}\) 表示当前状态为 \(i\),加入 \(A\) 或 \(B\),走向哪一个状态,\(val_{i,0/1}\) 为转移的代价。
-
状态 \(\text{A}\)。如果加入 \(A\),走向状态 \(\text{AA}\),点没有移动,代价为 \(0\)。如果加入 \(B\),走向状态 \(\text{AB}\),点没有移动,代价为 \(0\)。
-
状态 \(\text{AA}\)。如果加入 \(A\),走向状态 \(\text{AAA}\),点没有移动,代价为 \(0\)。如果加入 \(B\),走向状态 \(\text{AB}\),点向后移动一格,代价为 \(1\)。
-
状态 \(\text{AAA}\)。如果加入 \(A\),走向状态 \(\text{AAAA}\),点后退一步到上一个 \(B\),代价为 \(1\)。如果加入 \(B\),可以先向后再向前越过 \(AAA\) 到下一个 \(B\),也可以爬两步 \(A\),即走向状态 \(\text{O}\),代价为 \(2\)。
-
状态 \(\text{AAAA}\)。注意这里状态不同于以前,当前点为 \(B\)。如果加入 \(A\),走向状态自己 \(\text{AAAA}\),点不动,代价为 \(0\)。如果加入 \(B\),向前越过 \(AAAA\) 到下一个 \(B\),即走向状态 \(\text{B}\),代价为 \(1\)。
-
状态 \(\text{AB}\)。如果加入 \(A\),可以向后走到 \(B\),也可以跳到下一个 \(A\),即走向状态 \(O\),代价为 \(1\)。如果加入 \(B\),虽然没有连续 \(4\) 个 \(B\),但一定会从 \(A\) 跳过这段 \(B\),即状态 \(\text{BBBB}\),代价为 \(0\)。
-
状态 \(\text{OA}\)。如果加入 \(A\),虽然没有连续 \(4\) 个 \(A\),但一定会把 \(O\) 当作 \(B\) 跳过这段 \(A\),即状态 \(\text{AAAA}\),代价为 \(0\)。如果加入 \(B\),可以向后走到 \(A\),也可以把 \(O\) 当作 \(B\) 跳到下一个 \(B\),即走向状态 \(\text{O}\),代价为 \(1\)。
-
状态 \(\text{O}\)。如果加入 \(A\),走向状态 \(\text{OA}\),代价为 \(0\)。如果加入 \(B\),走向状态 \(\text{OB}\),代价为 \(0\)。
分析完自动机状态的转移,dp 即可。设 \(dp_{i,j,k}\) 表示加入前 \(i\) 位,走了 \(j\) 步,当前状态为 \(k\)。
注意到加入一位并不是走到一位。状态设定的当前走到的点不是状态的最后一位。记录 \(dis\) 表示状态的最后一位是 \(n-1\),状态设定的当前走到的点离终点的距离,稍微处理即可。
复杂度 \(O(13nk)\)。
code
int n,m,ans;
char c[maxn];
int tree[15][2],val[15][2];
int dis[7];
int dp[maxn][maxn][15];
int T;
signed main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
n=read();m=read();
scanf("%s",c+1);
tree[0][0]=2;val[0][0]=0;tree[0][1]=8;val[0][1]=0;
tree[2][0]=4;val[2][0]=0;tree[2][1]=8;val[2][1]=1;
tree[4][0]=6;val[4][0]=1;tree[4][1]=12;val[4][1]=2;
tree[6][0]=6;val[6][0]=0;tree[6][1]=1;val[6][1]=1;
tree[8][0]=12;val[8][0]=1;tree[8][1]=7;val[8][1]=0;
tree[10][0]=6;val[10][0]=0;tree[10][1]=12;val[10][1]=1;
tree[12][0]=10;val[12][0]=0;tree[12][1]=11;val[12][1]=0;
for(int i=0;i<=5;i++){//状态取反。
for(int k=0;k<=1;k++){
if(tree[i*2][k^1]==12)tree[i*2+1][k]=tree[i*2][k^1];
else tree[i*2+1][k]=tree[i*2][k^1]^1;
val[i*2+1][k]=val[i*2][k^1];
}
}
// for(int i=0;i<=12;i++)cout<<tree[i][0]<<" "<<val[i][0]<<" "<<tree[i][1]<<" "<<val[i][1]<<"\n";
dis[0]=dis[3]=dis[4]=dis[5]=dis[6]=1;
dis[1]=dis[2]=2;
dp[0][0][12]=1;
for(int i=0;i<=n-2;i++){
for(int j=0;j<=m;j++){
for(int k=0;k<=12;k++){
if(dp[i][j][k]){
if(c[i+1]=='A'){
dp[i+1][j+val[k][0]][tree[k][0]]+=dp[i][j][k];
dp[i+1][j+val[k][0]][tree[k][0]]%=mod;
}
else if(c[i+1]=='B'){
dp[i+1][j+val[k][1]][tree[k][1]]+=dp[i][j][k];
dp[i+1][j+val[k][1]][tree[k][1]]%=mod;
}
else{
dp[i+1][j+val[k][0]][tree[k][0]]+=dp[i][j][k];
dp[i+1][j+val[k][0]][tree[k][0]]%=mod;
dp[i+1][j+val[k][1]][tree[k][1]]+=dp[i][j][k];
dp[i+1][j+val[k][1]][tree[k][1]]%=mod;
}
}
}
}
}
for(int j=0;j<=m;j++){
for(int k=0;k<=12;k++)if(j+dis[k>>1]<=m){
ans+=dp[n-1][j][k];
ans%=mod;
}
}
printf("%lld\n",ans);
}