BZOJ1264: [AHOI2006]基因匹配Match
Dp·树状数组
题解:
http://www.cppblog.com/MatoNo1/archive/2011/03/19/142240.html?opt=admin
LCS问题的朴素时间复杂度为
O(NM)
。对于本题显然需要优化。
观察LCS的转移方程:
F[i][j] = F[i-1][j-1]+1
(当A[i]==B[j]时)
F[i][j] = max{F[i-1][j], F[i][j-1]}
(当A[i]!=B[j]时)
可以将F用滚动数组来表示,即设F’为上阶段的F(即F[i-1]),则本阶段的F(即F[i])可以由F’求得:
F[j] = F'[j-1]+1
(当A[i]==B[j]时)
F[j] = max{F'[j], F[j-1]}
(当A[i]!=B[j]时)
进一步,这个F’其实都不用记录,只需在每一阶段更新一遍F即可:
F[j] = F[j-1]+1
(当A[i]==B[j]时)
F[j] = max{F[j], F[j-1]}
(当A[i]!=B[j]时)
不过需要逆序更新(保证F[j-1]是上一阶段的而不是本阶段的),这与01背包有点像。
由题意可以发现,A[i]==B[j]的出现次数极少,在每阶段中只会出现5次!我们可以预先求出这5个地方的值,然后对于其它的F[j],其在本阶段的值其实就是它前面的最大值(max{F[1..j-1]}),又因为我们最后只需知道F[N’](N’=5N,即序列长度)即可,故可设计出以下算法:
一开始F[1..N]均为0,然后将以下内容执行N’次,第i次:
(1)求出B序列中与A[i]相等的5个元素的位置,设为S[1..5];
(2)依次更新F[S[5..1]],每个都更新为它前面的最大值加1,其它的值暂时不管;
N’次执行完后,整个序列中的最大值就是F[N’]的值。由于这个算法中出现的主要操作是改动一个指定位置元素的值和找一个前缀区间中的最大值,因此可以采用树状数组,时间复杂度 O(NlogN) (线段树必TLE)。
【总结:在本题中使用了一种“推迟更新”的方法,即需要更新一个值时,先暂时不理它,等到需要引用到它的时候再更新。这种方法最常见的应用就是线段树的结点标记。不过要注意的是,如果该值的推迟更新会对它后面要更新的值带来问题(也就是,这些后更新的值需要引用该值的新值),就不能使用这种方法。在本题中,其它位置的值的改变只与这5个特殊的位置有关,与其它因素无关,故可以使用这种方法。】
Code:
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 100005;
int n,x,pos[N][6],f[N],ans;
struct BIT{
int c[N], sz;
void init(int _sz){ sz=_sz; memset(c,0,sizeof(c)); }
void upd(int x,int d){ while(x<=sz){ c[x]=max(c[x],d); x+=x&(-x); } }
int mx(int x){ int ans=0; while(x>0){ ans=max(ans,c[x]); x-=x&(-x); } return ans; }
} bit;
int main(){
scanf("%d",&n); n*=5; bit.init(n);
for(int i=1;i<=n;i++){
scanf("%d",&x); pos[x][++pos[x][0]]=i;
}
for(int i=1;i<=n;i++){
scanf("%d",&x);
for(int j=5;j;j--){
int k=pos[x][j];
f[k]=max(f[k],bit.mx(k-1)+1);
bit.upd(k,f[k]);
ans=max(ans,f[k]);
}
}
printf("%d\n",ans);
}