bzoj 3173: [Tjoi2013]最长上升子序列(离线二分+树状数组)

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

Time Limit: 10 Sec   Memory Limit: 128 MB
Submit: 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;
}


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值