bzoj3173: [Tjoi2013]最长上升子序列

来源:互联网 发布:网络安全意识 责任心 编辑:程序博客网 时间:2024/06/17 18:12

题意

共n次操作,第i次操作在第xi个数后插入数字i并询问当前最长上升子序列。
n<=100000。
这个我也理解错了很多次

题解

前言

如果把原序列弄出来的话,就可以xjb做了,这个十分简单,就不再赘述了。这里我主要说一下怎么把原序列弄出来。当然,弄出来如果用Splay的话,也很简单,我也不说了,我说一下用二分加树状数组的做法。话说我弄这个想了好几个小时,然后看了做法又弄了好久,荒废了一个下午

做法

我们考虑倒着插入所有数
因为能对他的位置造成的,只有在他后面的数,于是我们考虑倒着插入所有的数
然后我们发现,他最终的位置时这样确定的:
如果在比他先插入的数里面,有一个插入的位置比他当前的小,那么他的位置就++。然后用新的位置继续去寻找,这个地方还比较好理解
然后他最后的话一定是到达一个最小的位置,使得在这个位置前面出现的数刚好等于他移动的差值,写成代码就是(get1就是在这个位置之前的)

get1(mid)==mid-a[u]

然后我们神奇地发现他还是可以二分的,这个感性可能很难理解,于是我们要理性地认识一下
我们发现,mid-a[u]其实是一个递增一次函数,且斜率为1
然后get1呢,是一个递增的函数,但是有多快不知道
但是我们可以确定,他mid每次加1,他变化的大小肯定不会超过1,也就是他肯定不会有上面的一次函数优秀
然后我们发现,当在0点的时候前者就是0,后者是-a[u],画成图像是这样的这里写图片描述
黑色的是get1,红色的是另外一个
由于斜率的原因,他们至多会重合一段,然后就不再重合了,由于两个都是递增,于是我们就可以愉快地使用二分来找出重合的最小值了

然后这个方法就说完了。。

CODE:

#include<cstdio>#include<cstdlib>#include<cstring>#include<iostream>#include<algorithm>using namespace std;const int N=100005;int n;int a[N];int s[N];//搞完后的序列int ss[N];int lb (int x){return x&(-x);}int f1[N];int get1 (int x){    int lalal=0;    while (x>=1)    {        lalal+=f1[x];        x-=lb(x);    }    return lalal;}void change1 (int x,int y){    while (x<=n)    {        f1[x]+=y;        x+=lb(x);       }}void prepare(){    for (int u=n;u>=1;u--)    {        int l=a[u],r=n;        while (l<=r)        {            int mid=(l+r)>>1;               if (get1(mid)<=mid-a[u]) r=mid-1,ss[u]=mid;            else l=mid+1;        }        change1(ss[u],1);    }    for (int u=1;u<=n;u++) s[ss[u]]=u;}int ans[N];//插入这个数的时候的答案int f[N];int get (int x){    int lalal=0;    while (x>=1)    {        lalal=max(lalal,f[x]);        x-=lb(x);    }    return lalal;}void change (int x,int y){    while (x<=n)    {        f[x]=max(f[x],y);        x+=lb(x);       }}void solve (){    for (int u=1;u<=n;u++)    {        ans[s[u]]=get(s[u])+1;        change(s[u],ans[s[u]]);    }    for (int u=2;u<=n;u++) ans[u]=max(ans[u],ans[u-1]);    for (int u=1;u<=n;u++) printf("%d\n",ans[u]);}int main(){    scanf("%d",&n);    for (int u=1;u<=n;u++)  scanf("%d",&a[u]);      for (int u=1;u<=n;u++) a[u]++;    prepare();    solve();    return 0;}
原创粉丝点击