POJ 2828 Buy Tickets(算法竞赛进阶指南, 树状数组,二分)

算法竞赛进阶指南,261页, 树状数组,二分查找
题目意思:
有 n 个人,每个人有一个权值,初始队列没有人,这 n 个人进行 n 次插队,每次插队代表一个权值为 val 的人插入第 x 个位置后。
最后从前往后输出每个人的权值。 题目类似于 POJ 2182

本题要点:
1、 虽然一个人现在插在 x 位置后,但是它最终的位置不一定是 x+1 ,再后来人的插入后可能位置还会变。
但是,最后一个人插在 x 位置,这个人最后就是在 x+1 位置上;倒数第二个人,只用将最后一个人的位置"留空",
再找第 x 个没有"留空"的位置插入即可。
2、 初始化一个数组b, 每个位置要么是0,要么是1; 用树状数组来维护数组b,
然后从后往前扫描数组a:
1)寻找数组b中第 p[i].pos 个1的下标 idx, 这个下标就是第i人的排队的位置
2)将数组b的第 idx 个1置零。实际就是 树状数组 update(idx, -1);
3、 用二分查找来寻找数组b中第 p[i].pos 个1的下标

#include <cstdio>
#include <cstring>
#include <iostream>
#define lowbit(i) ((i)&(-i))
using namespace std;
const int MaxN = 200010;
int a[MaxN];
//int b[MaxN]; //计算逻辑上的b数组,存放的是 01 序列
int c[MaxN];
int ans[MaxN];
int n;

struct Person
{
	int pos, val;
}p[MaxN];

int getSum(int k)
{
    int res = 0;
    while(k)
    {
        res += c[k];
        k ^= lowbit(k);		//这里使用 异或来代替 k -= lowbit(k), 计算速度快一点
    }
    return res;
}

void update(int x, int v)
{
	for(int i = x; i <= n; i += lowbit(i))
	{
		c[i] += v;	
	}
}

bool judge(int mid, int s)
{
	return getSum(mid) >= s;
}

int find_index(int s)
{
	int L = 1, R = n + 1, mid;
	while(L < R)
	{
		mid = (L + R) / 2;	
		if(judge(mid, s))
		{
			R = mid;
		}else{
			L = mid + 1;
		}
	}
	return L;
}

void solve()
{
	memset(c, 0, (n + 1) * sizeof(int));
	for(int i = 1; i <= n; ++i)
	{
		update(i, 1);	
	}
	int idx;
	for(int i = n; i >= 1; --i)
	{
		idx = find_index(p[i].pos);
		ans[idx] = p[i].val;	
		update(idx, -1);
	}
	for(int i = 1; i < n; ++i)
	{
		printf("%d ", ans[i]);
	}
	printf("%d\n", ans[n]);
}

int main()
{
	while(scanf("%d", &n) != EOF)
	{
		for(int i = 1; i <= n; ++i)
		{
			scanf("%d%d", &p[i].pos, &p[i].val);
			p[i].pos++;
		}
		solve();
	}
	return 0;
}

/*
4
0 77
1 51
1 33
2 69
4
0 20523
1 19243
1 3890
0 31492
*/

/*
77 33 69 51
31492 20523 3890 19243
*/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值