codevs2245 股票趋势 ( 二维线段树+dp )

本文探讨了一个特定的字符串匹配问题——有间距限制的最长公共子序列(GLCS)。通过使用二维线段树优化算法的时间复杂度,解决了在间距限制下找到两个字符串的最长公共子序列的问题。

codevs2245 股票趋势

原题地址http://codevs.cn/problem/2245/

题意:
T组数据。
给出两个由大写字母组成的字符串序列,计算两串序列的相似度,且还有一个限制,兩个相似点( 相同字母) 的前后间距不能太远,否则相似度会被扭曲。
将此问题定义为「有间距限制的最长共同子序列」(GLCS, gapped longest common subsequence)问题。

假设第一个序列称为α,第二个序列称为β。例如,α =“ACBDCAA”,β=“ADDBCDBAC” 。兩者在无间距限制的情形下,其 LCS 可为“ADCA” , “ABCA” ,或“ACBC” ,长度为4。假设间距限制如下:

A 2, B 0, C 3, D 0

上述间距之意义为,如果字母A 被选入LCS 中,则与其前一个被选入的字母之间,在α序列最多只能有2 个未被选入的字母,在β序列亦同。α与β在上述间距限制的情形,GLCS 可为“ACA” 或“ACC” ,长度为3。

对于无间距限制的情形,可将每个字母的间距视为无限大。本题的答案只要输出GLCS 的长度即可。

数据范围
1 ≤ T≤ 5
每个串的长度<=800
0<=限制间距<=400

题解:
最初想法:
定义dp[i][j]为以A的前i个字符与B的前j个字符且以 i/j 结尾的LCS
dp[i][j]=max( dp[sta~eda][atb~edb] +1)
sta=max(i-lim[i]-1,0), eda=i-1
stb=max(j-lim[j]-1,0), edb=j-1
但这个转移方程是O(n^4)的。

注意到转移方程等价于求矩形的最大值并带修改。
于是考虑二维线段树O(n^2 logn^2)
其实就是两维,第一维的每个节点对应第二维的一棵树。
空间动态开。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#define LL long long
using namespace std;
const int N=805;
const int inf=0x3f3f3f3f;
char A[N],B[N];
int la,lb,lim[30],T,dp[N][N],root=0,tail=0;
struct node
{
    node(int ls=0,int rs=0,int mx=0,int root=0){}
    int ls,rs,mx,root;
}tr[20000000];
int query(int &nd,int lf,int rg,int L ,int R)
{
    if(!nd)
    {
        nd=++tail;
        tr[nd]=tr[0];
    }
    if(L<=lf&&rg<=R) return tr[nd].mx;
    int mid=(lf+rg)>>1;
    int mx=0;
    if(L<=mid) mx=max(mx,query(tr[nd].ls,lf,mid,L,R));
    if(R>mid) mx=max(mx,query(tr[nd].rs,mid+1,rg,L,R));
    return mx;
}
int query(int &nd,int lf,int rg,int L,int R,int l,int r)
{
    if(!nd)
    {
        nd=++tail;
        tr[nd]=tr[0];
    }
//
    if(L<=lf&&rg<=R) return query(tr[nd].root,0,lb,l,r);
    int mid=(lf+rg)>>1;
    int mx=0;
    if(L<=mid) mx=max(mx,query(tr[nd].ls,lf,mid,L,R,l,r));
    if(R>mid) mx=max(mx,query(tr[nd].rs,mid+1,rg,L,R,l,r));
    return mx;
}
void update(int nd)
{
    int ls=tr[nd].ls; int rs=tr[nd].rs;
    tr[nd].mx=max(tr[nd].mx,max(tr[ls].mx,tr[rs].mx));
}
void modify(int &nd,int lf,int rg,int pos,int val)
{

    if(!nd)
    {
        nd=++tail;
        tr[nd]=tr[0];
    }
    if(lf==rg)
    {
        tr[nd].mx=max(tr[nd].mx,val);
        return;
    }
    int mid=(lf+rg)>>1;
    if(pos<=mid) modify(tr[nd].ls,lf,mid,pos,val);
    else modify(tr[nd].rs,mid+1,rg,pos,val);
    update(nd);

}
void modify(int &nd,int lf,int rg,int a,int b,int val)
{

    if(!nd)
    {
        nd=++tail;
        tr[nd]=tr[0];
    }

    modify(tr[nd].root,0,lb,b,val);
    if(lf==rg)  return;
    int mid=(lf+rg)>>1;
    if(a<=mid)
    modify(tr[nd].ls,lf,mid,a,b,val);
    else modify(tr[nd].rs,mid+1,rg,a,b,val);

}
int main()
{
    freopen("glcs.in","r",stdin);
    freopen("glcs.out","w",stdout);
    scanf("%s",A+1); scanf("%s",B+1);
    la=strlen(A+1); lb=strlen(B+1);
    scanf("%d",&T);
    for(int tt=1;tt<=T;tt++)
    {
        int ans=0;
        memset(lim,-1,sizeof(lim));
        memset(dp,0,sizeof(dp));
        tail=0; tr[0].ls=tr[0].rs=tr[0].mx=tr[0].root=0;root=0; tr[1]=tr[0];
        while(1)
        {
            char ss[5];
            scanf("%s",ss);
            if(ss[0]=='$') break;
            int len;
            scanf("%d",&len);
            lim[ss[0]-'A']=len;
        }
        for(int i=1;i<=la;i++)
        {
            for(int j=1;j<=lb;j++)
            {
                if(A[i]!=B[j]) dp[i][j]=0;
                else
                {
                    int sta,stb,eda,edb;
                    if(lim[A[i]-'A']==-1)  sta=0;
                    else sta=max(0,i-lim[A[i]-'A']-1);
                    if(lim[B[j]-'A']==-1)  stb=0;
                    else stb=max(0,j-lim[B[j]-'A']-1);
                    eda=i-1; edb=j-1; 
                    int ret=0;  //dp[sta~eda][atb~edb]
                    if(sta<=eda) ret=query(root,0,la,sta,eda,stb,edb);
                    dp[i][j]=ret+1;
                    modify(root,0,la,i,j,dp[i][j]);
                    ans=max(ans,dp[i][j]);

                }
            }   
        }   
        printf("%d",ans);   
        if(tt!=T) printf(" ");
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值