hdu 6284 Longest Increasing Subsequence LIS

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6284

 

题意:

       给你n个数,其中有一些数为0,问把这些0从1变到n之后分别形成的最长公共子序列和为多少。

 

做法:

     

      因为所有0一次只能变成某一个数,所以很明显对LIS的贡献最多只有1,假设我们原来得到的LIS是len,那么原先答案就可以是len*n*(n+1)/2,那么什么数会使LIS+1呢,假设有一个原LIS序列为1 4 6 8,那么假设1 和4之间有一个0,那么就能在0变成2和3的时候LIS都加1,所以我们能发现,对于一个0来说,假设它前面存在一个数xi后面有一个数xj,使得 到i的最长上升序列数+从j开始到最后的最长下降序列数==len(LIS),那么这个0变成[xi+1,xj-1]之内的任何数都可以在答案中被添加。

      所以我们要先用正常的nlogn的方法求出对于每个数,到这里的LIS长度en[i]和从这里开始到最后的LIS长度st[i],在最后for的时候有个小技巧,每次从后面往前面枚举,遇到0的时候做中断,在中断之后可以更新新的数组arr,这个数组里保存的是,当后面st[i]最大的数,即后面还需要长度为1,为2....的LIS的最大值,因为可能会遇到1  2  0  6 3,这样的情况, 1 2 6和1 2 3都为最大,但是我们要取的最优的答案是(1,6)区间内的,所以要进行这样的更新,另外还需要考虑的一个操作是对于st[i]==len和en[i]==len的情况,如果st[i]==len,那么从1到x[i]-1之内的数都可以被取到,如果是en[i]==len那么从x[i]到n的数也都可以取。具体实现看代码。


 

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(int)(a);i<=(int)(b);i++)
using namespace std;
typedef long long ll;
const int maxn=100005;

int st[maxn],en[maxn],a[maxn];
int up[maxn],nup,n,add[maxn];
int ra(int x){
    int ret=lower_bound(up+1,up+1+nup,x)-up;
    up[ret]=x;
    if(ret==nup+1) nup++;
    return ret;
}
int main(){
    while(~scanf("%d",&n)){
        int fi=-1,la=-1;
        rep(i,0,n) st[i]=en[i]=add[i]=up[i]=0;
        rep(i,1,n) {
            scanf("%d",&a[i]);
            if(a[i]!=0){
                if(fi==-1){
                    fi=i;
                    up[1]=a[i];
                }
                la=i;
            }
        }
        if(fi==-1){
            printf("%lld\n",(ll)n*(n+1)/2);
            continue;
        }
        en[fi]=1,st[la]=1;
        nup=1;
        rep(i,fi+1,n){
            if(a[i]!=0){
                en[i]=ra(a[i]);
            }
        }
        up[1]=-a[la];
        nup=1;
        for(int i=la-1;i>=1;i--){
            if(a[i]!=0){
                st[i]=ra(-a[i]);
            }
        }
        rep(i,1,n) up[i]=0;
        int p=n;
        while(p>0){
            int nowst=p;
            while(p>0){
                if(a[p]==0) {up[0]=n+1; break;}
                int ne=nup-en[p];
                if(up[ne]-1>a[p]){
                    add[a[p]+1]++;
                    add[up[ne]]--;
                }
                p--;
            }
            for(int i=nowst;i>p;i--) up[st[i]]=max(up[st[i]],a[i]);
            if(up[nup]-1>a[p]&&p){
                add[up[nup]]--;
                add[a[p]+1]++;
            }
            p--;
        }

        ll ans=0;
        rep(i,1,n){
            add[i]=add[i-1]+add[i];
            if(add[i]>0) ans+=(ll)(nup+1)*i;
            else ans+=(ll)i*nup;
        }
        printf("%lld\n",ans);
    }
    return 0;
}
/*
3
0 2 0
*/

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值