arc119f 题解

arc119f

自动机写法。开始在做的时候题解没讲每个节点代表什么状态,自己推了一遍,记录一下。

思路

计数,求有多少种替换方式使得 00nn 存在一条长度小于等于 KK 的路径。

可以做 O(n3)O(n^3) 的 dp。设 dpi,a,bdp_{i,a,b} 表示前 ii 个位置,最近的 AABB 分别在 aabb。官方题解是优化 aabb,发现有意义状态下 aabb 的差不超过一定数值。

观察发现,在 BAA...AAB\text{BAA...AAB} 处,肯定选择从 BB 直接跳过去。具体的,当有连续的 44AA 后,会选择直接从两端的 BB 越过。所以有效的状态并不多。

考虑建一个自动机来求出任意状态对应的下一个状态。由于 AABB 可以取反得到,先只考虑当前在 AA,在 BB 同理。以下几种有用状态。

  • 状态 00。当前在 AA,上一个位置为 BB,记为 A\text{A}

  • 状态 11 即状态 00 反过来,记为 B\text{B}

  • 状态 22。当前在 AA,上一个位置为 BB,下一个位置为 AA,记为 AA\text{AA}

  • 状态 44。当前在 AA,上一个位置为 BB,下两个位置为 AAAA,记为 AAA\text{AAA}

  • 状态 66。当前在 AA,上一个位置为 BB,下三个位置为 AAAAAA,记为 AAAA\text{AAAA}。此时如果往后有 BB,直接先后退越过;否则走向自己,不会有 55AA 的状态。注意到此时无论如何都会后退,所以稍微调整一下。定义状态 66 为当前在 BB,接下来有连续 AA 且一定会选择跳过。

  • 状态 88。当前在 AA,上一个位置不重要,下一个位置为 BB,记为 AB\text{AB}。此时可以跳到下一个 AA,也可以爬到 BB,但不知道走一步后要去 AA 还是 BB

  • 状态 10101212。当前既可以当作在 AA,也可以当作在 BB,记为 O\text{O},称为状态 1212。状态 1010 表示当前在 OO,下一个为 AA,记为 OA\text{OA}

综上,有 1313 种有用状态。

  • A\text{A}AA\text{AA}AAA\text{AAA}AAAA\text{AAAA}AB\text{AB}

  • 上面 55 种状态取反得到当前在 BB 的另外 55 种。

  • OA\text{OA},OB\text{OB},O\text{O}

考虑转移。记 treei,0/1tree_{i,0/1} 表示当前状态为 ii,加入 AABB,走向哪一个状态,vali,0/1val_{i,0/1} 为转移的代价。

  • 状态 A\text{A}。如果加入 AA,走向状态 AA\text{AA},点没有移动,代价为 00。如果加入 BB,走向状态 AB\text{AB},点没有移动,代价为 00

  • 状态 AA\text{AA}。如果加入 AA,走向状态 AAA\text{AAA},点没有移动,代价为 00。如果加入 BB,走向状态 AB\text{AB},点向后移动一格,代价为 11

  • 状态 AAA\text{AAA}。如果加入 AA,走向状态 AAAA\text{AAAA},点后退一步到上一个 BB,代价为 11。如果加入 BB,可以先向后再向前越过 AAAAAA 到下一个 BB,也可以爬两步 AA,即走向状态 O\text{O},代价为 22

  • 状态 AAAA\text{AAAA}。注意这里状态不同于以前,当前点为 BB。如果加入 AA,走向状态自己 AAAA\text{AAAA},点不动,代价为 00。如果加入 BB,向前越过 AAAAAAAA 到下一个 BB,即走向状态 B\text{B},代价为 11

  • 状态 AB\text{AB}。如果加入 AA,可以向后走到 BB,也可以跳到下一个 AA,即走向状态 OO,代价为 11。如果加入 BB,虽然没有连续 44BB,但一定会从 AA 跳过这段 BB,即状态 BBBB\text{BBBB},代价为 00

  • 状态 OA\text{OA}。如果加入 AA,虽然没有连续 44AA,但一定会把 OO 当作 BB 跳过这段 AA,即状态 AAAA\text{AAAA},代价为 00。如果加入 BB,可以向后走到 AA,也可以把 OO 当作 BB 跳到下一个 BB,即走向状态 O\text{O},代价为 11

  • 状态 O\text{O}。如果加入 AA,走向状态 OA\text{OA},代价为 00。如果加入 BB,走向状态 OB\text{OB},代价为 00

分析完自动机状态的转移,dp 即可。设 dpi,j,kdp_{i,j,k} 表示加入前 ii 位,走了 jj 步,当前状态为 kk

dp0,0,12=1dp_{0,0,12}=1

dpi+1,j+valk,0,treek,0+=dpi,j,kdp_{i+1,j+val_{k,0},tree_{k,0}}+=dp_{i,j,k}

dpi+1,j+valk,1,treek,1+=dpi,j,kdp_{i+1,j+val_{k,1},tree_{k,1}}+=dp_{i,j,k}

注意到加入一位并不是走到一位。状态设定的当前走到的点不是状态的最后一位。记录 disdis 表示状态的最后一位是 n1n-1,状态设定的当前走到的点离终点的距离,稍微处理即可。

复杂度 O(13nk)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];
signed main(){
	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);
}