kmjp's blog

競技プログラミング参加記です

yukicoder : No.1494 LCS on Tree

方針はすぐ立つけど、実装は若干面倒かもしれない。
https://yukicoder.me/problems/no/1494

問題

N頂点の木を成す無向グラフが与えられる。
各点には、文字が与えられている。

str(x,y)を、点xからyへの最短経路に対し、経路上の点の文字を順につなげたものとする。

文字列Sが与えられる。
任意のx,yを選べるとき、str(x,y)とSのLCSの最大長を求めよ。

解法

頂点vの部分木に対し
pref(v,k) := vの葉wからvまでの全パスにおいて、Sのprefix k文字とstr(w,v)のLCSの最大長
suff(v,k) := vからvの葉wまでの全パスにおいて、Sのsuffix k文字とstr(v,w)のLCSの最大長
とすると、解の候補は

  • S[i]と点vの文字が一致するなら1+pref(v,i)+suff(v,|S|-i)
  • S[i]と点vの文字が一致しないならpref(v,i)+suff(v,|S|-i)

となる。
あとは子から順にpref(v,*)とsuff(v,*)を求めれば、O(N*|S|)で済む。

int N,L;
string S;
vector<pair<int,char>> E[2020];
int pre[2020][2020];
int suf[2020][2020];
int ret;

void dfs(int cur,int p) {
	int i,j;
	FORR2(e,c,E[cur]) if(p!=e) {
		dfs(e,cur);
		FOR(i,L) {
			suf[e][i]=max(suf[e][i],(c==S[i])+suf[e][i+1]);
		}
		for(i=L-1;i>=0;i--) suf[e][i]=max(suf[e][i],suf[e][i+1]);
		for(i=L;i>=1;i--) {
			pre[e][i]=max(pre[e][i],(c==S[i-1])+pre[e][i-1]);
		}
		for(i-1;i<=L;i++) pre[e][i]=max(pre[e][i],pre[e][i-1]);
		FOR(i,L+1) {
			suf[cur][i]=max(suf[cur][i],suf[e][i]);
			pre[cur][i]=max(pre[cur][i],pre[e][i]);
		}
	}
	FOR(i,L+1) {
		vector<int> A,B,C;
		A.push_back(0);
		B.push_back(0);
		C.push_back(0);
		A.push_back(0);
		B.push_back(0);
		C.push_back(0);
		FORR2(e,c,E[cur]) if(p!=e) {
			A.push_back(suf[e][i]);
			B.push_back(suf[e][i]);
			C.push_back(pre[e][i]);
		}
		sort(ALL(B));
		reverse(ALL(B));
		FOR(j,A.size()) {
			if(A[j]==B[0]) ret=max(ret,C[j]+B[1]);
			else ret=max(ret,C[j]+B[0]);
		}
		
	}
	
}


void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>N>>S;
	L=S.size();
	FOR(i,N-1) {
		cin>>x>>y>>s;
		E[x-1].push_back({y-1,s[0]});
		E[y-1].push_back({x-1,s[0]});
	}
	
	dfs(0,0);
	cout<<ret<<endl;
}

まとめ

こういう一見典型なテクニック(LCS)をちょっと前提を変えてだいぶ解法が変わるのは面白いね。