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

题意

共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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值