poj之旅——2104

题目:给出一个数列,对于每个查询(i,j,k),输出ai.....aj中排序后第k个数。


分析:

其实就是线段树的运用。

二分答案,然后对于i...j计算小于k的数字个数,这个用线段树模拟归并排序,然后如果当前区间被i....j包含,那么就返回这个区间的二分后的结果,否则查找子区间。

跟AC的程序对拍十几个极端数据,可是死都不过,不懂。线段树就是我的克星。

参考程序:

#include<cstdio>
#include<algorithm>
#include<vector>
#include<cstring>
using namespace std;
const int maxn=110000;
vector<int> c[4*maxn];
int a[maxn],b[maxn];
int n;
void build(int p,int l,int r){
	if (l+1==r)c[p].push_back(a[l]);
	else{
		int mid=(l+r)>>1;
		int lch=2*p,rch=2*p+1;
		build(lch,l,mid);
		build(rch,mid,r);
		c[p].resize(r-l);
		merge(c[lch].begin(),c[lch].end(),c[rch].begin(),c[rch].end(),c[p].begin());
	}	
}
int query(int p,int l,int r,int ql,int qr,int x){
	if (qr<=l || r<=ql)return 0;else
	if (ql<=l && r<=qr){
		return upper_bound(c[p].begin(),c[p].end(),x)-c[p].begin();
	}else{
		int mid=(l+r)>>1,t=0;
		if (ql<mid)t+=query(2*p,l,mid,ql,qr,x);
		if (mid<qr)t+=query(2*p+1,mid,r,ql,qr,x);
		return t;
	}
}
int main(){
	int T;
	while (scanf("%d%d",&n,&T)==2){
	    for (int i=1;i<=n;i++){
		    scanf("%d",&a[i]);
		    b[i]=a[i];
	    } 
	    sort(b+1,b+n+1);
	    build(1,1,n+1);
    	while (T--){
	    	int ql,qr,K;
		    scanf("%d%d%d",&ql,&qr,&K);
    	    int l=-1,r=n+1;
	        while (l<r){
		        int mid=(l+r)>>1;
		        if (query(1,1,n+1,ql,qr+1,b[mid])>=K)r=mid;
			    else l=mid+1;
		    }
		    printf("%d\n",b[l]);
		}
	}
	return 0;
}

正确的:

#include<iostream>  
#include<cstring>  
#include<cstdio>  
#include<vector>  
#include<cmath>  
#include<algorithm>  
#define maxn 100000  
using namespace std;  
  
vector<int> dat[4*maxn + 50];    //线段树的数据  
int a[maxn + 50];  
int n, q;  
  
//构建线段树  
//k是节点的编号,和区间[l, r)对应  
void build(int k, int l, int r)  
{  
    if (r - l == 1) {  
        dat[k].push_back(a[l]); return;  
    }  
    int lc = k << 1, rc = k << 1 | 1;  
    build(lc, l, (l + r) / 2);  
    build(rc, (l + r) / 2, r);  
    dat[k].resize(r - l);  
    //利用STL的merge函数把两个儿子的数列合并  
    merge(dat[lc].begin(), dat[lc].end(), dat[rc].begin(), dat[rc].end(),dat[k].begin());  
}  
  
//计算[i, j)中不超过x的数的个数  
//k是节点的编号,和区间[l, r)对应  
int query(int i, int j, int x, int k, int l, int r)  
{  
    if (j <= l || r <= i)  
        //完全不相交  
        return 0;  
    else if (i <= l&&r <= j){  
        //完全包含在里面  
        return upper_bound(dat[k].begin(), dat[k].end(), x) - dat[k].begin();  
    }  
    else {  
        //对儿子递归地计算  
        int lcnt = query(i, j, x, k << 1, l, (l + r) / 2);  
        int rcnt = query(i, j, x, k << 1 | 1, (l + r) / 2, r);  
        return lcnt + rcnt;  
    }  
}  
  
int search(int x, int y, int k)  
{  
    int l = -1000000000 - 1;  
    int r = -l + 2;  
    while (l < r){  
        int mid = (l + r) >> 1;  
        int num = query(x, y+1, mid, 1, 1, n+1);  
        if (k <= num) r = mid;  
        else{  
            l = mid + 1;  
        }  
    }  
    return l;  
}  
  
int main()  
{  freopen("make.out","r",stdin);
	freopen("out.out","w",stdout);
    while (cin >> n >> q)  
    {  
        for (int i = 1; i <= n; i++){  
            scanf("%d", a + i);  
        }  
        build(1, 1, n + 1);  
        int li, ri, ki;  
        for (int i = 0; i < q; i++){  
            scanf("%d%d%d", &li, &ri, &ki);  
            printf("%d\n", search(li, ri, ki));  
        }  
    }  
    return 0;  
}  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值