BZOJ 1264 动态规划 + 树状数组

   “很难想的一道题不过不难写”—— Po


     这道题窝的思考过程十分坎坷。


    首先想着纯DP,类似于NOIP Day2T2的方法搞。


    设f[i][j]为强制将A[i]B串中第j个相同字符匹配的LCSs[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;
}


posted @ 2015-12-14 19:22  Armeria  阅读(149)  评论(0编辑  收藏  举报