【bzoj4553】【Tjoi2016】【Heoi2016】【序列】【树套树】【线段树套线段树】

题目大意

给出长度为n的序列a,有m次变化。每次变化把a[x]改成b[x],一个位可以多次修改,变化相对独立。选出最长字串,在m次变化与原串中都是不下降的。

题解

通过分析可知,设f[i]为前i位中一定选了i所形成的最长不下降字串的长度,f[i]可以转移到f[j] (j>i)当且仅当满足三个条件。a[i]<=a[j]。max(b[i])<=a[j]。a[i]<=min(b[j])。把a[i]也当作b[i],则要满足max(b[i])<=a[j]。a[i]<=min(b[j])。我们可以O(n^2)地dp。
其实我们只用满足两个关键字都小于或等于当前两个关键字就可以转移,我们考虑线段树套线段树。外层线段树代表第一关键字,内层代表第二关键字,把f值放进去,单点修改,区间查询就可以了。
详细点就是外层的每个区间[l,r]都拥有一颗关于第二关键字的线段树,每个包含第一关键字的[l,r]都需要按第二关键字在自己的线段树上插入f值。再详细就是看代码了。
其实这题可以用cdq分治,或k-d tree,或其他树套树来做。

code

#include<set>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define fo(i,j,k) for(int i=j;i<=k;i++)
#define fd(i,j,k) for(int i=j;i>=k;i--)
using namespace std;
int const maxn=100000;
int n,m,pon=1,a[maxn+10],mx[maxn+10],mi[maxn+10],son[maxn*108+10][3];
void change2(int now,int l,int r,int mxi,int fi){
    int m=(l+r)/2;
    son[now][2]=max(son[now][2],fi);
    if(l==r)return;
    else if(mxi<=m){
        if(!son[now][0])son[now][0]=++pon;
        change2(son[now][0],l,m,mxi,fi);
    }else{
        if(!son[now][1])son[now][1]=++pon;
        change2(son[now][1],m+1,r,mxi,fi);
    }
}
void change(int now,int l,int r,int ai,int mxi,int fi){
    int m=(l+r)/2;
    if(!son[now][2])son[now][2]=++pon;
    change2(son[now][2],1,maxn+1,mxi,fi);
    if(l==r)return;
    else if(ai<=m){
        if(!son[now][0])son[now][0]=++pon;
        change(son[now][0],l,m,ai,mxi,fi);
    }else{
        if(!son[now][1])son[now][1]=++pon;
        change(son[now][1],m+1,r,ai,mxi,fi);
    }
}
int get2(int now,int l,int r,int a0,int ai){
    int m=(l+r)/2;
    if((l==a0)&&(r==ai))return son[now][2];
    else if(ai<=m){
        if(!son[now][0])return 0;
        return get2(son[now][0],l,m,a0,ai);
    }else{
        int tmp=0;
        if(son[now][1])tmp=get2(son[now][1],m+1,r,m+1,ai);
        if(son[now][0])tmp=max(tmp,son[son[now][0]][2]);
        return tmp;
    }
}
int get(int now,int l,int r,int mi0,int mii,int a0,int ai){
    int m=(l+r)/2;
    if((l==mi0)&&(r==mii)){
        if(!son[now][2])return 0;
        return get2(son[now][2],1,maxn+1,a0,ai);
    }
    else if(mii<=m){
        if(!son[now][0])return 0;
        return get(son[now][0],l,m,mi0,mii,a0,ai);
    }else{
        int tmp=0;
        if(son[now][1])tmp=get(son[now][1],m+1,r,m+1,mii,a0,ai);
        if(son[now][0]&&son[son[now][0]][2])tmp=max(tmp,get2(son[son[now][0]][2],1,maxn+1,a0,ai));
        return tmp;
    }
}
int main(){
    //freopen("len.in","r",stdin);
    //freopen("len.out","w",stdout);
    freopen("d.in","r",stdin);
    freopen("d.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,1,n)scanf("%d",&a[i]),a[i]++,mx[i]=mi[i]=a[i];
    fo(i,1,m){
        int x,y;scanf("%d%d",&x,&y);y++;
        mx[x]=max(mx[x],y);
        mi[x]=min(mi[x],y);
    }
    int ans=1;
    change(1,1,maxn+1,1,1,0);
    fo(i,1,n){
        int f;
        ans=max(ans,f=get(1,1,maxn+1,1,mi[i],1,a[i])+1);
        change(1,1,maxn+1,a[i],mx[i],f);
    }
    printf("%d",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值