方針はすぐ立つけど、実装は若干面倒かもしれない。
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)をちょっと前提を変えてだいぶ解法が変わるのは面白いね。