公式

F - More ABC 解説 by mechanicalpenciI


以下では、\(S\)\(i\) 文字目 \((1\leq i\leq \lvert S\rvert)\)\(S_i\) で表し、\(S_i\ldots S_j\)\(S\)\(i\) 文字目から \(j\) 文字目までの部分文字列を表します。
また、文字列 \(T\) について、\(f(T)\)\(T\) に含まれる ABC の数を表します。
加えて、区別のため、高橋君が操作を行う前の(すなわち入力として与えられる)文字列を \(S\), いくつかの文字を置換した後の文字列を \(S'\) とします。

この問題を動的計画法で解くことを考えます。
\(dp[i][j]\) \((0\leq i\leq N,0\leq j\leq K)\) を、「\(S_1\ldots S_i\) より \(S'_1\ldots S'_i\)ABC\(j\) 個多く含むために置換する必要のある文字数の最小値(不可能な場合は \(+\infty\))」として定義します。

初期状態は \(dp[i][0]=0\) \((0\leq i\leq N)\), \(dp[0][j]=\infty\) \((1\leq j\leq K)\) であり、\(dp[N][K]\) が求める答えです。
なお、\(j<0\) については \(dp[i][j]>0=dp[i][0]\), \(j>K\) については \(dp[i][j]>dp[i][K]\geq dp[N][K]\) となるため、それぞれの状態から \(dp[N][K]\) への遷移が最適となることはないため、それらの状態を考える必要はありません。これは、次に述べる遷移が、

  • 最適な遷移においては値が常に単調増加すること
  • \(dp[i][j]\) の値は \(dp[i+1][j-1],dp[i+1][j],dp[i+3][j],dp[i+3][j+1]\) にのみ直接遷移(関係)し得ること

をみたす事から従います。

\(dp[i][j]\) \((1\leq i\leq N, 1\leq j\leq K)\)への遷移は次の \(2\) つの場合のみ考えれば良いです。

  • \(S'_{i-2}S'_{i-1}S'_i=\)ABC である場合
  • \(S'_{i-2}S'_{i-1}S'_i=\)ABC でない場合

特に \(i<3\) のときは後者の場合のみ考えれば良いことに注意してください。
それぞれの場合における、変更する必要のある文字数の最小値について考えます。

\(1\). \(S'_{i-2}S'_{i-1}S'_i=\)ABC である場合

このとき、\(f(S'_1S'_2\ldots S'_i)=f(S'_1S'_2\ldots S'_{i-3})+1\) となります。
よって、\(f(S'_1S'_2\ldots S'_{i-3})-f(S_1S_2\ldots S_{i-3})=f(S'_1S'_2\ldots S'_i)-1-f(S_1S_2\ldots S_i)+\{f(S_1S_2\ldots S_i)-f(S_1S_2\ldots S_{i-3})\}=j-1+\{f(S_1S_2\ldots S_i)-f(S_1S_2\ldots S_{i-3})\}\) であるため、
\(X_i\)\(f(S_1S_2\ldots S_i)-f(S_1S_2\ldots S_{i-3})\) \((0\leq X_i\leq 1)\)\(Y_i\)\(S_{i-2}S_{i-1}S_i\)ABC にするために変更する必要のある文字の個数 \((0\leq Y_i\leq 3)\) として定義すると、この場合において変更する必要のある文字の数の最小値は \(dp[i-3][j-1+X_i]+Y_i\) となります。

\(2\). \(S'_{i-2}S'_{i-1}S'_i=\)ABC でない場合

この場合については \(S'_i\) を変更しない場合のみについて考えれば良いです。なぜなら、\(S'_i\) を変更しかつ\(S'_{i-2}S'_{i-1}S'_i=\)ABC とならなかったとすると、 \(S'_1S'_2\ldots S'_{i-1}\bm{S'_i}\)\(S'_1S'_2\ldots S'_{i-1}\bm{S_i}\) を比較して、\(f(S'_1S'_2\ldots S'_{i-1}S'_i)\leq f(S'_1S'_2\ldots S'_{i-1}S_i)\) かつ後者の方が変更した文字の数が少ないため、 \(S'_1S'_2\ldots S'_{i-1}S'_i\) より少ない数の変更で、同じだけの ABC を含むような文字列を構成することが可能だからです。

さて、この場合、\(f(S'_1S'_2\ldots S'_i)=f(S'_1S'_2\ldots S'_{i-1})\) であり、\(1\). と同様に \(f(S'_1S'_2\ldots S'_{i-1})-f(S_1S_2\ldots S_{i-1})=j+\{f(S_1S_2\ldots S_i)-f(S_1S_2\ldots S_{i-3})\}\) であるため、\(Z_i\)\(f(S_1S_2\ldots S_i)-f(S_1S_2\ldots S_{i-1})\) \((0\leq Z_i\leq 1)\)で定義すると、この場合において変更する必要のある文字の数の最小値は \(dp[i-1][j+Z_i]\) となります。

よって、\(dp[i][j]=\min(dp[i-3][j-1+X_i]+Y_i,dp[i-1][j+Z_i])\) となります。どちらかが配列の定義域の外である場合には他方の値が入ります。\(Z_i=1\) ならば \(i\geq 3\) が成り立つためどちらも範囲外となることはないことに注意してください。

\(Z_i\)\(S_{i-2}S_{i-1}S_i=\) ABC ならば \(1\), そうでないならば \(0\), \(X_i=Z_{i-2}+Z_{i-1}+Z_i\) であるためそれぞれの \(i\) に対して定数時間で計算でき、\(Y_i\) もそれぞれの \(i\) に対して定数時間で計算できます。

それぞれの遷移も \(O(1)\) で計算できるため、全体で \(O(NK)\) で答えを求めることができます。
よって、この問題を解くことができました。

C++ による実装例:

#include <bits/stdc++.h>
using namespace std;

#define INF (int)1e+9

int main() {
	int t,n,k;
	string s;

	cin>>t;
	for(int itest=0;itest<t;itest++){
		cin>>s;
		cin>>k;
		n=s.size();

		vector<int>x(n+1),y(n+1),z(n+1);
		vector<vector<int> >dp(n+1,vector<int>(k+1));

		for(int i=3;i<=n;i++){
			if(s[i-3]!='A')y[i]++;
			if(s[i-2]!='B')y[i]++;
			if(s[i-1]!='C')y[i]++;
			if(y[i]==0)z[i]++;
			for(int j=0;j<3;j++){
				if(i+j<=n)x[i+j]+=z[i];
			}
		}

		for(int j=1;j<=k;j++)dp[0][j]=INF;
		for(int i=1;i<=n;i++){
			for(int j=1;j<=k;j++){
				dp[i][j]=INF;
				if(j+z[i]<=k)dp[i][j]=min(dp[i][j],dp[i-1][j+z[i]]);
				if(i>=3)dp[i][j]=min(dp[i][j],dp[i-3][j-1+x[i]]+y[i]);
			}
		}
		if(dp[n][k]>=INF)cout<<-1<<endl;
		else cout<<dp[n][k]<<endl;
	}
	return 0;
}

投稿日時:
最終更新: