题意
共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;
}