[bzoj4553][TJOI&HEOI2016]序列

题目大意

有一个长度为n的序列,有一些位置的值有可能变化。
一次变化最多只会改变一个位置上的值,题目告诉了你可能发生的变化情况。
求在任意可能情况下都能满足非降的最长子序列长度。

DP

先弄出偏序关系,设mi[i]表示位置i可能出现的最小值,mx[i]就是最大值。
那么对于 j<i ,j与i可以在一个合法子序列中的条件是:
1、a[j]<=mi[i]
2、mx[j]<=a[i]
设f[i]表示以i结尾最长合法子序列长度,那么显然这个DP我们只会n^2的。

整体二分

现在,相当于有n个询问。
第i个询问要求你求解f[i]。
那么我们将n个询问整体二分。
solve(l,r)表示解决第l~r个询问。
显然我们可以先去解决第l~mid个询问(solve(l,mid))
接下来考虑左边对右边的影响,那么我们可以让左边根据a值排序,右边根据mi值排序,然后用一个指针,这样可以满足第一个偏序条件。
对于第二个条件,用线段树维护。
然后,就成功处理了右边部分的上一个是左边部分的情况,而右边部分也有可能上一个不在左边部分,递归处理即可(solve(mid+1,r))
于是复杂度n log^2 n解决本题。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
const int maxn=100000+10,maxd=100000;
int tree[maxd*5];
bool bz[maxd*5];
int a[maxn],id[maxn],mi[maxn],mx[maxn],f[maxn];
int i,j,k,l,t,n,m,ans;
void mark(int p){
    tree[p]=0;
    bz[p]=1;
}
void down(int p){
    if (bz[p]){
        mark(p*2);
        mark(p*2+1);
        bz[p]=0;
    }
}
void change(int p,int l,int r,int a,int b){
    if (l==r){
        tree[p]=max(tree[p],b);
        return;
    }
    down(p);
    int mid=(l+r)/2;
    if (a<=mid) change(p*2,l,mid,a,b);else change(p*2+1,mid+1,r,a,b);
    tree[p]=max(tree[p*2],tree[p*2+1]);
}
int query(int p,int l,int r,int a,int b){
    if (l==a&&r==b) return tree[p];
    down(p);
    int mid=(l+r)/2;
    if (b<=mid) return query(p*2,l,mid,a,b);
    else if (a>mid) return query(p*2+1,mid+1,r,a,b);
    else return max(query(p*2,l,mid,a,mid),query(p*2+1,mid+1,r,mid+1,b));
}
bool cmpa(int x,int y){
    return a[x]<a[y];
}
bool cmpmi(int x,int y){
    return mi[x]<mi[y];
}
void solve(int l,int r){
    if (l==r){
        f[l]=max(f[l],1);
        return;
    }
    int mid=(l+r)/2;
    solve(l,mid);
    int i;
    fo(i,l,r) id[i]=i;
    sort(id+l,id+mid+1,cmpa);
    sort(id+mid+1,id+r+1,cmpmi);
    mark(1);
    int j=l;
    fo(i,mid+1,r){
        while (j<=mid&&a[id[j]]<=mi[id[i]]){
            change(1,1,maxd,mx[id[j]],f[id[j]]);
            j++;
        }
        f[id[i]]=max(f[id[i]],query(1,1,maxd,1,a[id[i]])+1);
    }
    solve(mid+1,r);
}
int main(){
    freopen("4553.in","r",stdin);freopen("4553.out","w",stdout);
    scanf("%d%d",&n,&m);
    fo(i,1,n) scanf("%d",&a[i]),mi[i]=mx[i]=a[i];
    fo(i,1,m){
        scanf("%d%d",&j,&k);
        mi[j]=min(mi[j],k);
        mx[j]=max(mx[j],k);
    }
    solve(1,n);
    ans=0;
    fo(i,1,n) ans=max(ans,f[i]);
    printf("%d\n",ans);
}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值