首页 > 代码库 > POJ 2828 Buy Tickets 线段树解法

POJ 2828 Buy Tickets 线段树解法

此题应用线段树的方法非常巧妙。没做过真的难想得出是这么想的。

是一个逆向思维的运用。

其实一看到这道题目我就想到要运用逆向思维的了,但是就是没那么容易想通的。

思路:

1 要从后面往前更新线段树

2 线段树记录的是当前区间还剩下多少个记录空间

3 因为后面的插入会使得前面的插入往后退,那么前面插入的下标如果大于前面可以插入的空间,就需要往后退了。

好难理解的操作。仔细观察一下下面update这个函数吧。

二分地去查找适合的插入位置,把插入操作时间效率提高到 O(lgn),如果使用一般方法插入操作效率就会达到O(n)。

const int SIZE = 200001;
const int TREESIZE = SIZE + (SIZE<<1);
int segTree[TREESIZE];
int pos[SIZE];
int val[SIZE];
int orderVal[SIZE];

inline int lChild(int rt) { return rt<<1; }
inline int rChild(int rt) { return rt<<1 | 1; }

void build(int l, int r, int rt)
{
	segTree[rt] = r - l + 1; //记录有多少个存储空间,以解决位置冲突
	if (l == r) return ;
	int m = l + ((r-l)>>1);
	build(l, m, lChild(rt));
	build(m+1, r, rChild(rt));
}

void update(int i, int p, int l, int r, int rt)
{
	segTree[rt]--;
	if (l == r)
	{
		orderVal[l] = val[i];//l为适当位置填写val
		return ;
	}
	int m = l + ((r-l)>>1);
	if (segTree[lChild(rt)] >= p) update(i, p, l, m, lChild(rt));
	//下面是精要的地方,十分巧妙的插队法。如果后面有人插队了,那么前面的人和前面插队的人都需要往后推
	else update(i, p - segTree[lChild(rt)], m+1, r, rChild(rt));
}

int main()
{
	int n;
	while(scanf("%d", &n) == 1) 
	{
		build(1, n, 1);
		for (int i = 1; i <= n; i++)
		{
			scanf("%d %d", &pos[i], &val[i]);
		}
		for (int i = n; i > 0; i--)
		{
			update(i, pos[i]+1, 1, n, 1);
		}
		for (int i = 1; i < n; i++)
		{
			printf("%d ", orderVal[i]);
		}
		printf("%d\n", orderVal[n]);
	}
	return 0;
}