「BSOJ3401」 建筑 - 主席树+二分

题目描述

小敏和小燕是一对好朋友。

他们正在玩一种神奇的游戏,叫Minecraft。

他们正在盖建筑,他们手上有不同的方块。

每种方块有不同的不美观度,他们持有每种方块的数量也不一样。

他们现在准备实行他们众多建筑计划中的一个。

他们的建筑计划有不同的要求,不美观度有上限和下限。为了方便拿取方块,他们也要求所用的方块在方块堆中的连续一段。每个建筑计划需要固定的方块数。

他们现在要知道,在最好情况下,每个建筑计划中最不美观的方块的不美观度是多少。

输入格式

第一行一个整数n,表示方块种类数。

接下来的n 行,第i+1 行有两个整数,pi和qi,分别表示在方块堆中的第i种方块的不美观度和数量。

接下来的一行一个整数m,表示建筑计划的数量。

接下来的m 行,每行五个整数l,r,s,t,k,分别代表该建筑计划的中所用的方块种类要在方块堆的[l,r]区间中,并且美观度要介于[s,t]中,且需要k 个方块。

输出格式

对于每个建筑计划,输出一行一个整数,表示该建筑计划最好情况下最不美观度的方块的不美观度。如果没有满足条件的建筑方案,输出-1。

数据范围

对于30%的数据,n<=10,m<=30

对于40%的数据,n<=100,m<=300

对于60%的数据,n<=1000,m<=3000

对于100%的数据,n<=30000,m<=30000

分析

要求最不美观度最小,可以考虑二分答案。设当前二分到的答案为 m i d mid mid,则问题转化为判定是否在制指定区间内存在 k k k个方块,且不美观度在 l l l m i d mid mid之间,若存在,则令 r = m i d r=mid r=mid,否则令 l = m i d + 1 l=mid+1 l=mid+1,继续进行上述二分。最后的答案为 l l l

现在的问题是如何求区间内小于等于指定数的个数。可以用主席树,即可持久化线段树,以权值为下标对每个值建立由前一个值转移过来的线段树。利用前缀和思想,可以求得在指定区间内小于等于指定数的个数。具体看代码。

由于是按照权值建立的主席树,所以要先离散化。

代码

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int N=30005,LogN=20;
int n,m,rt[N],s[N*LogN],nn,L[N*LogN],ans;
int val[N],num[N],tot,R[N*LogN];//s存在当前值域的数的个数 
int b[N*3],qs[N],qt[N],ql[N],qr[N],qk[N];
void Build(int &p,int l,int r) {
	p=++tot;
	if (l==r) return;
	int mid=(l+r)>>1;
	Build(L[p],l,mid);
	Build(R[p],mid+1,r);
}
void Insert(int &p,int rt,int l,int r,int v,int x) {//主席树插入 
	p=++tot;
	s[p]=s[rt];L[p]=L[rt];R[p]=R[rt];
	s[p]+=x;
	if (l==r) return;
	int mid=(l+r)>>1;
	if (v<=mid) Insert(L[p],L[rt],l,mid,v,x);
	else Insert(R[p],R[rt],mid+1,r,v,x);
}
void Ask(int p,int q,int l,int r,int ql,int qr) {
	if (ql<=l&&r<=qr) {//当前值域完全包含与所需值域 
		ans+=s[q]-s[p];//利用两个值之差得出在当前区间内落在当前值域的数的个数 
		return;
	}
	int mid=(l+r)>>1;
	if (ql<=mid) Ask(L[p],L[q],l,mid,ql,qr);
	if (qr>mid) Ask(R[p],R[q],mid+1,r,ql,qr);
}
int main() {
	scanf("%d",&n);
	for (int i=1;i<=n;i++) {
		scanf("%d%d",&val[i],&num[i]);
		b[i]=val[i];
	}
	nn=n;
	scanf("%d",&m);
	for (int i=1;i<=m;i++) {
		scanf("%d%d%d%d%d",&qs[i],&qt[i],&ql[i],&qr[i],&qk[i]);
		b[++nn]=ql[i];
		b[++nn]=qr[i];
	}
	sort(b+1,b+nn+1);//离散化 
	nn=unique(b+1,b+nn+1)-b-1;
	for (int i=1;i<=n;i++) val[i]=lower_bound(b+1,b+nn+1,val[i])-b;
	Build(rt[0],1,n);
	for (int i=1;i<=n;i++)//一个个在值域中插入数量 
		Insert(rt[i],rt[i-1],1,nn,val[i],num[i]);
	for (int i=1;i<=m;i++) {
		ql[i]=lower_bound(b+1,b+nn+1,ql[i])-b;
		qr[i]=lower_bound(b+1,b+nn+1,qr[i])-b;
		int l=ql[i],r=qr[i]+1,k=qk[i];
		int x=qs[i],y=qt[i];
		while (l<r) {//二分 
			int mid=(l+r)>>1;
			ans=0;
			Ask(rt[x-1],rt[y],1,nn,ql[i],mid);
			if (ans>=k) r=mid;
			else l=mid+1;
		}
		if (l>qr[i]) puts("-1");//无解 
		else printf("%d\n",b[l]);//输出离散化后对应的值 
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值