bzoj 3173: [Tjoi2013]最长上升子序列

来源:互联网 发布:拉尼娅王后知乎 编辑:程序博客网 时间:2024/06/05 23:05

题意:

每次将i插入当前第k个数字后面,求最长上升子序列。

题解:

假如这题不是插入,而是直接说放到第k位,且保证k不相同,那么就很好做了。
设f[i]表示以第i位为结尾的最长上升子序列。
加入一个值时就统计下它前面的最长上升子序列,加1就好了。因为他是最大的,所以显然对其他f值没有影响。
于是关键是怎么求出原序列。
如果强制在线的话直接平衡树无脑上就好了nlogn完全不虚。
但是可以离线,所以有一种很简便,但挺难想的做法。
将给出的序列反过来插入,对于i,假设i+1~n的数都插好了,怎么求i插在哪呢。
考虑二分,i插入的地方为mid,那么i被向后推了mid-X[i]格。换句话说,在已经插入的数中,mid前要有mid-X[i]个数。(因为无论怎么推,数与数间的相对位置是不会变的,所以插入的时候一定就在i前面)。
所以二分的判定条件是:getsum(mid)<=mid-x
getsum是树状数组求和。
至于为什么满足二分性,可以将两个函数大概的图像画出来,就显然了。
code:

#include<cstdio>#include<cstdlib>#include<cstring>#include<iostream>using namespace std;int a[100010],n,tr[100010];int lowbit(int x){return x&(-x);}void change(int k,int c){    for(int i=k;i<=n;i+=lowbit(i))        tr[i]=max(tr[i],c);}int get(int k){    int ans=0;    for(int i=k;i>=1;i-=lowbit(i))        ans=max(ans,tr[i]);    return ans;}void Change(int k,int c){    for(int i=k;i<=n;i+=lowbit(i))        tr[i]+=c;}int getsum(int k){    int ans=0;    for(int i=k;i>=1;i-=lowbit(i))        ans+=tr[i];    return ans;}int main(){    scanf("%d",&n);    for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]++;    for(int i=n;i>=1;i--)    {        int x=a[i];        int l=a[i],r=n;        while(l<=r)        {            int mid=(l+r)/2;            if(getsum(mid)<=mid-x) a[i]=mid,r=mid-1;            else l=mid+1;        }        Change(a[i],1);    }    int ans=0;    memset(tr,0,sizeof(tr));    for(int i=1;i<=n;i++)    {        int k=a[i],t=get(k-1)+1;        ans=max(ans,t);change(k,t);        printf("%d\n",ans);    }}
原创粉丝点击