“很難想的一道題不過不難寫”—— Po姐
這道題窩的思考過程十分坎坷。
首先想着純DP,類似於NOIP Day2T2的方法搞。
設f[i][j]爲強制將A[i]與B串中第j個相同字符匹配的LCS,s[i][j]爲考慮到A[i]與B串中第j個相同字符匹配(且強制不允許選這個字符之後)的LCS。
從這個狀態設計就可以看得出來它既麻煩又sb。
先解釋一下爲什麼要強制不允許選該字符之後——否則會造成交叉:之前的超過了當前字符。
這道題和NOIP2015 Day2T2有一個不同:前者是一個具有特殊性質的LCS問題,而後者需要有一個序列被完全匹配。
所以本題用DP就會產生一系列的問題。比如上面所述的那個狀態就有一個很嚴重的問題:s[1][1]應該是什麼?如果B串中第一個A[i]同字符出現在較後面的位置,那麼s[1][1]甚至無法轉移——因爲它依賴於後面的狀態,比如A[2]在B串中第一個同字符在A[1]之前,那麼就無法轉移了。
但是由這個失敗的DP我們也能得到一些啓示:我們的DP順序應是按照B串中字符的順序。如此一來,我們的狀態也可以得到簡化:f[i]表示以i結尾的LCS長度。考慮LCS的基本特點:當且僅當a[i]=b[j]時,f[i]+1。那麼,我們就只需記錄A串中每個字符出現的5個位置,按照B串的順序掃描。對於B[j],我們找到A串中對應的5個位置,然後f[pos]=max{f[k]| k<pos}+1。注意要按k倒序搞,不然會造成重複加。如何取得最大值呢?線段樹用不着:對於A[1..m]的連續區間的最大值,直接用樹狀數組即可——考慮樹狀數組求和的原理:一層一層地往上爬,把沿途的長條的值累和。這裏,我們只要把每段“長條”維護的值改爲本區間內最大值即可。注意各種地方n*5別忘了。
// BZOJ 1264
#include <cstdio>
#include <cstring>
using namespace std;
#define rep(i,a,b) for (int i=a; i<=b; i++)
#define read(x) scanf("%d", &x)
#define fill(a,x) memset(a, x, sizeof(a))
#define dep(i,a,b) for (int i=a; i>=b; i--)
const int N=20000+5, S=6;
int a[N*5], b[N*5], n, C[N*5], cnt[N], pos[N][S], f[N*5], ans=0;
int lowbit(int x) { return x&(-x); }
int max(int a, int b) { return a<=b ? b : a; }
int get_max(int x) {
int ret=0;
for( ; x; x-=x&-x) ret=max(ret, C[x]);
return ret;
}
void update(int x, int w) {
for( ; x<=n*5; x+=x&-x) C[x]=max(C[x], w);
}
int main()
{
read(n);
rep(i,1,5*n) cnt[i]=C[i]=f[i]=0;
rep(i,1,5*n) read(a[i]), pos[a[i]][++cnt[a[i]]]=i;
rep(i,1,5*n) read(b[i]);
rep(i,1,5*n)
dep(j,5,1) {
int p=pos[b[i]][j];
f[p]=max(f[p], get_max(p-1)+1);
update(p, f[p]);
ans=max(ans, f[p]);
}
printf("%d\n", ans);
return 0;
}