poj 2828 树状数组or线段树(还原排队的人的序列)

题意:人们一个一个的来排队并插队,按人到来的顺序给出每个人插队的位置(插在第几个人后面),并告知每个人的id号,输出最终队伍的情况。

思路:按照hint给的思路显然是n2复杂度,对于200000个点来说大了点。

1、按照与2182同样的思路可以用树状数组来做。只不过这次二分是求第i个人应该最终排在哪个位置。(树状数组版本2中,tree[i]存放的是1~i的空余位置,具体思路见代码注释)

2、线段树也能做,记录区间目前还剩的空位数量,每一次插入的时候,如果该节点左儿子的空余数量>x,那么只要在左儿子找就可以了;否则要在右儿子中找,此时x改为x-左儿子num。

树状数组代码:

#include <stdio.h>
#include <string.h>
#define N 200005
int n;
int a[N],res[N],s[N],tree[N];
int lowbit(x){
	return x&(-x);
}
void add(int x){
	int i;
	for(i = x;i<=n;i+=lowbit(i))
		tree[i]++;
}
int sum(int x){
	int i,ans=0;
	for(i = x;i>=1;i-=lowbit(i))
		ans += tree[i];
	return ans;
}
int bisearch(int x){
	int low,high,mid;
	low = 1,high = n;
	while(low < high){
		mid = (low+high)>>1;
		if(x + sum(mid) >= mid)
			low = mid+1;
		else
			high = mid;
	}
	return low;
}
int main(){
	freopen("a.txt","r",stdin);
	while(scanf("%d",&n)!=EOF){
		int i,j;
		memset(tree,0,sizeof(tree));
		for(i = 1;i<=n;i++)
			scanf("%d %d",&a[i],&s[i]);
		for(i = n;i>=1;i--){
			j = bisearch(a[i]);
			res[j] = s[i];
			add(j);
		}
		for(i = 1;i<=n;i++)
			printf("%d ",res[i]);
		putchar('\n');
	}
	return 0;
}

树状数组代码,版本2:

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <cstdlib>
using namespace std;
#define clc(s,t) memset(s,t,sizeof(s))
#define INF 0x3fffffff
#define N 200005
int tree[N],a[N],s[N],res[N];
int n;
int lowbit(int x){
    return x&(-x);
}
void add(int i,int x){
    for(int j = i;j<=n;j+=lowbit(j))
        tree[j] += x;
}
int sum(int i){
    int res = 0;
    for(int j = i;j>=1;j-=lowbit(j))
        res += tree[j];
    return res;
}
int search(int d,int x){//相当于要插入的位置是d右侧的第x个空位上
    int high,mid,low,tmp;
    low = d;
    high = n;
    while(low < high){
        mid = (low+high)>>1;
        tmp = sum(mid) - sum(d);
        if(tmp < x)//二分时要注意:tmp==x是不能直接输出mid,因为mid处可能已经填过东西,所有要找到最靠左的tmp==x处
            low = mid+1;
        else
            high = mid;
    }
    return low;
}
int main(){
    while(scanf("%d",&n) != EOF){
        int i,j;
        clc(tree,0);
        for(i = 1;i<=n;i++){
            scanf("%d %d",&a[i],&s[i]);
            add(i,1);
        }
        for(i = n;i>=1;i--){
            j = search(a[i]+1,a[i]+1-sum(a[i]+1));//a[i]+1-sum(a[i]+1)表示要插入的位置左侧已经插入了多少个人
            add(j,-1);
            res[j] = s[i];
        }
        for(i = 1;i<=n;i++)
            printf("%d ",res[i]);
        putchar('\n');
    }
    return 0;
}

线段树代码:

#include <stdio.h>
#include <string.h>
#define N 200005
struct node{
	int left,right;
	int num;
}p[N<<2];
int a[N],s[N],res[N],n;
void create(int id,int left,int right){
	int mid;
	p[id].left = left;
	p[id].right = right;
	if(left == right){
		p[id].num = 1;
		return;
	}
	mid = (left+right)>>1;
	create(id<<1,left,mid);
	create((id<<1)+1,mid+1,right);
	p[id].num = p[id<<1].num + p[(id<<1)+1].num;
}
int query(int id,int x){
	p[id].num--;
	if(p[id].left == p[id].right)
		return p[id].left;
	if(p[id<<1].num <= x)
		query((id<<1)+1,x-p[id<<1].num);
	else
		query(id<<1,x);
}
int main(){
	freopen("a.txt","r",stdin);
	while(scanf("%d",&n)!=EOF){
		int i,j;
		for(i = 1;i<=n;i++)
			scanf("%d %d",&a[i],&s[i]);
		create(1,1,n);
		for(i = n;i>=1;i--){
			j = query(1,a[i]);
			res[j] = s[i];
		}
		for(i = 1;i<=n;i++)
			printf("%d ",res[i]);
		putchar('\n');
	}
	return 0;
}



你可能感兴趣的:(poj 2828 树状数组or线段树(还原排队的人的序列))