题目描述
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[]记录,设当前二分到的答案为midmidmid,needneedneed为该国家所需要的陨石样本,则将所有k≤midk\le midk≤mid的陨石雨在树状数组中进行区间修改,枚举X[]X[]X[],并进行单点查询,获得该国家所有的陨石样本为cntcntcnt,若cnt≥needcnt\ge needcnt≥need,则令R=midR=midR=mid,否则令L=mid−1L=mid-1L=mid−1,继续进行上述二分。
但现在有多个国家查询,所以可以将局部二分转移到整体二分上。定义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)>>1mid=(L+R)>>1mid=(L+R)>>1,则对于操作区间内的修改操作,即发生陨石雨,若发生时间d≤midd\le midd≤mid,则在树状数组中进行区间修改,并加入左边的缓存数组;否则加入右边的缓存数组。若为查询操作,则遍历该国家所有的地点,在树状数组里进行单点查询,并累加在cntcntcnt里,最后若cnt>=needcnt>=needcnt>=need,则加入左边的缓存数组,否则让该城市的need−=cntneed-=cntneed−=cnt,并加入右边缓存数组。结束后遍历撤销对树状数组的修改,并将左右缓存数组并入原数组,并递归调用左右两部分。因为是在值域上进行二分的,所以又称为基于值域的分治算法。
这样下来,若递归到L==RL==RL==R,则遍历操作区间并更新答案为LLL;若l>rl>rl>r,则没有操作,返回。时间复杂度为O(nlog2n)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;
}