BZOJ1264: [AHOI2006]基因匹配Match

本文介绍BZOJ1264基因匹配问题的优化算法,利用滚动数组和树状数组将时间复杂度降低至O(NlogN)。通过预处理匹配位置并逆序更新数组,实现高效求解最长公共子序列。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值