Description
维护一个序列,使它可以进行下面两种操作:
1.在末尾添加一个数字x
2.将整个序列变成第x次操作后的样子
在每次操作后,输出当前序列的最长上升子序列的长度
序列初始时为空
Input
输入文件lis.in的第一行有一个正整数n,表示操作个数。接下来n行每行有两个整数op,x。如果op为0,则表示添加x这个数字;如果op为1,则表示回到第x次操作之后。
Output
对于每次操作,在输出文件lis.out中输出一个答案,表示当前最长上升子序列的长度
Sample Input
5
0 2
0 0
1 0
1 0
0 5
Sample Output
1
1
0
0
1
【样例说明】
第一次操作后,序列为 2
第二次操作后,序列为2 0
第三次操作后,序列为(空)
第四次操作后,序列为(空)
第五次操作后,序列为 5
Data Constraint
30%的数据 n<=1000
另外20%的数据没有第二个操作
80%的数据 n<=200000
100%的数据 n<=500000且所有输入的数字都是长整型范围内的非负整数
想法:
这题前50分能水80分
f[i]表示最长上升子序列为i,第i位最小是多少
其实我们可以把所有操作变成一棵树,0操作就i-1与i连边,1操作x与i连边
然后从0开始遍历一棵树,1操作就什么都不变,0操作就改变f[],并把相应的改变记录
n太大卡dfs,猥琐的人工栈
Code
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int maxN=500010;
int n,i,a[maxN],b[maxN],x,y,z,
tot,next[maxN],last[maxN],tov[maxN],
f[maxN],tail,ans[maxN],l,r,mid;
void insert(int x,int y){
tov[++tot]=y,next[tot]=last[x],last[x]=tot;
}
struct zhj{
int x,z,ch,wz,qz;
};
zhj pre[maxN];
int main(){
freopen("lis.in","r",stdin);
freopen("lis.out","w",stdout);
scanf("%d",&n);
for (i=1;i<=n;i++){
scanf("%d%d",&a[i],&b[i]);
if (a[i]==0)
insert(i-1,i);
else insert(b[i],i);
}
ans[0]=0;
tot=0;
tail=1;
pre[1].x=0;
pre[1].z=last[0];
while (tail>0){
i=pre[tail].z;
y=tov[i];
if (y==19)
{
i=1;
}
if (a[y]==0){
if (f[tot]<b[y]){
f[++tot]=b[y];
ans[y]=tot;
pre[++tail].x=y;
pre[tail].z=last[y];
pre[tail].ch=1;
}
else{
l=1;r=tot;
while (l<r){
mid=(l+r)/2;
if (f[mid]>=b[y]) r=mid;else l=mid+1;
}
z=f[l];
f[l]=b[y];
ans[y]=tot;
pre[++tail].x=y;
pre[tail].z=last[y];
pre[tail].ch=0;
pre[tail].wz=l;
pre[tail].qz=z;
}
}
else{
ans[y]=tot;
pre[++tail].x=y;
pre[tail].z=last[y];
pre[tail].ch=2;
}
while ((pre[tail].z==0)&&(tail>=0)){
if (pre[tail].ch==1)
f[tot]=0,tot--;
if (pre[tail].ch==0)
f[pre[tail].wz]=pre[tail].qz;
tail--;
pre[tail].z=next[pre[tail].z];
}
}
for (i=1;i<=n;i++)
printf("%d\n",ans[i]);
}