3173: [Tjoi2013]最长上升子序列
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 2051 Solved: 1041
[ Submit][ Status][ Discuss]
Description
给定一个序列,初始为空。现在我们将1到N的数字插入到序列中,每次将一个数字插入到一个特定的位置。每插入一个数字,我们都想知道此时最长上升子序列长度是多少?
Input
第一行一个整数N,表示我们要将1到N插入序列中,接下是N个数字,第k个数字Xk,表示我们将k插入到位置Xk(0<=Xk<=k-1,1<=k<=N)
Output
N行,第i行表示i插入Xi位置后序列的最长上升子序列的长度是多少。
Sample Input
3
0 0 2
Sample Output
1
1
2
nlogn单调递增:http://blog.csdn.net/jaihk662/article/details/52064213
偷偷建议这题不要看题解!
……
这题的条件其实大大降低了题目难度,首先最后的序列一定是1-n的全排列
其次每次都是加上当前最大的数,这个性质很重要
这就意味着你只要得出最终的序列,然后套一下nlogn的单调递增就可以求出所有答案
令best[x]表示以数字x结尾的最长序列,ans[x]就是第x个答案
那么ans[x]=max(best[i] (1<=i<=x) )
主要是如何求出最终的序列
倒过来处理,很明显最后一个数所添加的位置一定是最终的位置,然后看下面的例子
假设样例为:
5
0 0 2 1 1
初始序列:
0 0 0 0 0(这一行就是最终序列,为0表示上面没有数字)
0 1 2 3 4(这一行表示前面有多少空位)
5加入第一个数字后面:
0 5 0 0 0
0 0 1 2 3
4加入第一个数字后面:
0 5 4 0 0
0 0 0 1 2
3加入第二个数字后面:
0 5 4 0 3
0 0 0 1 1
2加入最前面:
2 5 4 0 3
x x x 0 0(x的位置已经不可能再放数字了,可以理解为-1)
最后:
2 5 4 1 3(ok)
很显然的:树状数组维护前缀和,然后每次二分求第k个前缀最小
搞定
#include<stdio.h>
#include<algorithm>
using namespace std;
int n, len, cha[100005], tre[100005], a[100005], best[100005], ans[100005];
void Update(int x, int val)
{
while(x<=n)
{
tre[x] += val;
x += x&-x;
}
}
int Query(int x)
{
int sum = 0;
while(x)
{
sum += tre[x];
x -= x&-x;
}
return sum;
}
int Bsech(int x)
{
int l, r, m;
l = 0, r = len;
while(l<r)
{
m = l+(r-l)/2;
if(best[m]>=x)
r = m;
else
l = m+1;
}
return l;
}
int main(void)
{
int i, l, r, m, pos, now;
scanf("%d", &n);
for(i=1;i<=n;i++)
{
scanf("%d", &cha[i]);
Update(i, 1);
}
for(i=n;i>=1;i--)
{
l = 1, r = n;
cha[i] += 1;
while(l<r)
{
m = (l+r)/2;
if(Query(m)<cha[i])
l = m+1;
else
r = m;
}
a[r] = i;
Update(r, -1);
}
len = 1;
best[1] = a[1], ans[a[1]] = 1;
for(i=2;i<=n;i++)
{
if(a[i]>best[len])
best[++len] = a[i], ans[a[i]] = len;
else
{
pos = Bsech(a[i]);
ans[a[i]] = pos;
best[pos] = a[i];
}
}
now = 0;
for(i=1;i<=n;i++)
{
now = max(now, ans[i]);
printf("%d\n", now);
}
return 0;
}