「BZOJ2527」 Meteors - 整体二分

题目描述

BIU有N个成员国。现在它发现了一颗新的星球,这颗星球的轨道被分为M份(第M份和第1份相邻),第i份上有第Ai个国家的太空站。

这个星球经常会下陨石雨。BIU已经预测了接下来K场陨石雨的情况。

BIU的第i个成员国希望能够收集Pi单位的陨石样本。你的任务是判断对于每个国家,它需要在第几次陨石雨之后,才能收集足够的陨石。

输入格式

第一行是两个数N,M。

第二行有M个数,第i个数Oi表示第i段轨道上有第Oi个国家的太空站。

第三行有N个数,第i个数Pi表示第i个国家希望收集的陨石数量。

第四行有一个数K,表示BIU预测了接下来的K场陨石雨。

接下来K行,每行有三个数Li,Ri,Ai,表示第K场陨石雨的发生地点在从Li顺时针到Ri的区间中(如果Li<=Ri,就是Li,Li+1,…,Ri,否则就是Ri,Ri+1,…,m-1,m,1,…,Li),向区间中的每个太空站提供Ai单位的陨石样本。

输出格式

N行。第i行的数Wi表示第i个国家在第Wi波陨石雨之后能够收集到足够的陨石样本。如果到第K波结束后仍然收集不到,输出NIE。

数据范围

1<=n,m,k<=3*10^5
1<=Pi<=10^9
1<=Ai<10^9

分析

若只查询一个国家AAA显然是可以枚举的。考虑二分答案。将AAA所在的地方用数组X[]X[]X[]记录,设当前二分到的答案为midmidmidneedneedneed为该国家所需要的陨石样本,则将所有k≤midk\le midkmid的陨石雨在树状数组中进行区间修改,枚举X[]X[]X[],并进行单点查询,获得该国家所有的陨石样本为cntcntcnt,若cnt≥needcnt\ge needcntneed,则令R=midR=midR=mid,否则令L=mid−1L=mid-1L=mid1,继续进行上述二分。

但现在有多个国家查询,所以可以将局部二分转移到整体二分上。定义Solve(l,r,L,R)Solve(l,r,L,R)Solve(l,r,L,R)为要解决操作区间[l,r][l,r][l,r],当前值域为[L,R][L,R][L,R]。为了更方便地进行二分,将所查询的每个国家也看作一个操作。令mid=(L+R)&gt;&gt;1mid=(L+R)&gt;&gt;1mid=(L+R)>>1,则对于操作区间内的修改操作,即发生陨石雨,若发生时间d≤midd\le middmid,则在树状数组中进行区间修改,并加入左边的缓存数组;否则加入右边的缓存数组。若为查询操作,则遍历该国家所有的地点,在树状数组里进行单点查询,并累加在cntcntcnt里,最后若cnt&gt;=needcnt&gt;=needcnt>=need,则加入左边的缓存数组,否则让该城市的need−=cntneed-=cntneed=cnt,并加入右边缓存数组。结束后遍历撤销对树状数组的修改,并将左右缓存数组并入原数组,并递归调用左右两部分。因为是在值域上进行二分的,所以又称为基于值域的分治算法。

这样下来,若递归到L==RL==RL==R,则遍历操作区间并更新答案为LLL;若l&gt;rl&gt;rl>r,则没有操作,返回。时间复杂度为O(nlog⁡2n)O(n\log^2n)O(nlog2n)

对于本题来讲,值域为[1,k][1,k][1,k],但为了方便判断是否有解,查询时值域为[1,k+1][1,k+1][1,k+1],若最后答案为k+1k+1k+1则无解。

需要注意的是,由于本题数据范围很大,树状数组和累加值要开long longlong\ longlong long,还有在累加是若已经大于等于needneedneed了,就breakbreakbreak,防止炸long longlong\ longlong long

代码

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
using namespace std;
const int N=300005;
typedef long long LL;
struct Query {
	int x,y,z,d,id;
}q[N<<2],lq[N<<2],rq[N<<2];
int n,m,ans[N];
int qnum,k;
vector<int> vc[N];//用与存每个国家的太空站位置 
struct Bit {//树状数组相关
	LL c[N<<1],res;
	void Add(int x,int v) {for (;x<=m;x+=x&-x) c[x]+=v;}
	LL Ask(int x) {for (res=0;x;x-=x&-x) res+=c[x];return res;}
}t;
void Solve(int l,int r,int L,int R) {//整体二分
	if (l>r) return;//没有操作
	if (L==R) {//二分到底
		for (int i=l;i<=r;i++)
			if (q[i].id) ans[q[i].id]=L;
		return;
	}
	int mid=(L+R)>>1;
	int lt=0,rt=0;
	for (int i=l;i<=r;i++) {
		if (!q[i].id) {
			if (q[i].d<=mid) {//修改且时间在mid之前
				t.Add(q[i].x,q[i].z);
				t.Add(q[i].y+1,-q[i].z);
				lq[++lt]=q[i];
			} else rq[++rt]=q[i];
		}
	}
	for (int i=l;i<=r;i++) {
		if (q[i].id) {
			LL cnt=0;
			for (vector<int>::iterator it=vc[q[i].id].begin();it!=vc[q[i].id].end();it++) {
				cnt+=t.Ask(*it);
				if (cnt>=q[i].y) break;
			}
			if (cnt>=q[i].y) lq[++lt]=q[i];//修改当前值>=q[i].y(need),左 
			else {
				q[i].y-=cnt;//减去贡献
				rq[++rt]=q[i];//右
			}
		}
	}
	for (int i=l;i<=r;i++)
		if (!q[i].id&&q[i].d<=mid) {//撤销树状数组修改
			t.Add(q[i].x,-q[i].z);
			t.Add(q[i].y+1,q[i].z);
		}
	for (int i=1;i<=lt;i++) q[l+i-1]=lq[i];//并
	for (int i=1;i<=rt;i++) q[l+lt+i-1]=rq[i];
	Solve(l,l+lt-1,L,mid);//递归二分
	Solve(l+lt,r,mid+1,R);
}
int main() {
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++) {
		int val;
		scanf("%d",&val);
		vc[val].push_back(i);//注意,这里是将环断成链,要开两倍
		vc[val].push_back(i+m);
	}
	for (int i=1;i<=n;i++) {
		int need;
		scanf("%d",&need);
		q[++qnum]=(Query){0,need,0,0,i};
	}
	scanf("%d",&k);
	for (int i=1;i<=k;i++) {
		int l,r,v;
		scanf("%d%d%d",&l,&r,&v);
		if (l>r) r+=m;//l>r则为环的链接处,r+=m即可
		q[++qnum]=(Query){l,r,v,i,0};
	}
	m*=2;//断环成链
	Solve(1,qnum,1,k+1);
	for (int i=1;i<=n;i++) {
		if (ans[i]>k) printf("NIE\n");
		else printf("%d\n",ans[i]);
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值