BZOJ 1264 动态规划 + 树状数组
“很难想的一道题不过不难写”—— 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; }