BZOJ 2527 Meteors(整体二分)

50 篇文章 0 订阅
26 篇文章 0 订阅

Description
有n个国家和m个空间站,每个空间站都属于一个国家,一个国家可以有多个空间站,所有空间站按照顺序形成一个环,也就是说,m号空间站和1号空间站相邻。现在,将会有k场流星雨降临,每一场流星雨都会给区间[li,ri]内的每个空间站带来ai单位的陨石,每个国家都有一个收集陨石的目标pi,即第i个国家需要收集pi单位的陨石。
询问:每个国家最早完成陨石收集目标是在第几场流星雨后,如果所有流星雨结束后某个国家还未完成收集目标则输出NIE
Input
第一行为两个整数n和m表示国家数和空间站数,第二行m个整数oi表示每个空间站所属国家,第三行n个整数表示每个国家的收集目标need,第四行为一整数k表示流星雨次数,最后k行每行三个整数li,ri,ai表示该场流星雨都会给区间[li,ri]内的每个空间站带来ai单位的陨石
(1<=n<=m<=300000,1<=oi<=n,1<=need<=10^9,1<=ai<=10^9)
Output
独处每个国家最早完成陨石收集目标是在第几场流星雨,如果所有流星雨结束后某个国家还未完成收集目标则输出NIE
Sample Input
3 5
1 3 2 1 3
10 5 7
3
4 2 4
1 3 1
3 5 2
Sample Output
3
NIE
1
Solution
整体二分
对于单个查询(假设为第i个国家),我们可以二分k,每次对于一个区间[l,r],手动模拟一下在第mid场流星雨过后,第i个国家一共收集到了多少单位的陨石,如果比pi大,那么答案在[l,mid]范围内,否则答案在[mid+1,r]范围内。
对于多组查询,我们也可以这么做。首先,我们需要用一个列表id[]记录所有查询的编号,刚开始的时候,id[]自然是递增的.同时,我们用一个数组cur[i]记录下,第i个国家在l-1场流星雨过后,收集到的陨石的数目。
主过程为divide(int head,int tail,int l,int r),表示对于id[head]到id[tail]的所有询问,在[l,r]范围内查询答案,通过上一层的操作,我们保证id[head]到id[tail]的所有询问的答案都在[l,r]范围内。
首先,我们先模拟[l,mid]这么多次操作(在询问重新划分之后,必须要再次模拟,将数组清空),用树状数组或者是线段树计算出在[l,mid]场流星雨之后,每个空间站收集到的陨石的数目。
然后我们查询,每个国家收集到的陨石的数目,要注意的是,我们需要用链表储存每个国家对应的空间站,并且一一枚举,用tmp[id[i]]表示国家id[i]收集到的陨石的数目。
那么从[1,mid]这么多次操作之后,国家id[i]收集到的陨石数目就是temp[id[i]]+cur[id[i]],如果temp[id[i]]+cur[id[i]]>p[id[i]],那么表明对于国家id[i],其答案在[l,mid]这个范围内,否则其答案在[mid+1,r]范围内,并将temp[id[i]]累加到cur[id[i]]上。
具体操作看代码~
Code

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
#define maxn 333333
#define INF 1000000000ll
struct query
{
    int id;
    ll need,cur;
}q[maxn],q1[maxn],q2[maxn];
struct node
{
    int l,r;
    ll a;
}p[maxn];
int n,m,k,num,cnt;
int fir[maxn],next[maxn],ans[maxn];
ll b[maxn];
void init()
{
    memset(fir,0,sizeof(fir)); 
    memset(b,0,sizeof(b));
}
int lowbit(int x)
{
    return x&(-x);
}
void update(int x,ll v)
{
    for(int i=x;i<=m;i+=lowbit(i))b[i]+=v;
}
ll sum(int x)
{
    ll ans=0;
    for(int i=x;i>0;i-=lowbit(i))ans+=b[i];
    return ans;
}
void adjust(int l,int r,ll v)
{
    update(l,v),update(r+1,-v);
}
void divide(int head,int tail,int l,int r)
{
    if(head>tail)return ;
    if(l==r)
    {
        for(int i=head;i<=tail;i++)
            ans[q[i].id]=l;
        return ;
    }
    int mid=(l+r)>>1,res1=0,res2=0;
    for(int i=l;i<=mid;i++)
        if(p[i].l<=p[i].r)adjust(p[i].l,p[i].r,p[i].a);
        else adjust(p[i].l,m,p[i].a),adjust(1,p[i].r,p[i].a);
    for(int i=head;i<=tail;i++)
    {
        ll temp=0;
        for(int j=fir[q[i].id];j&&temp<INF;j=next[j])temp+=sum(j);
        if(q[i].cur+temp>=q[i].need)q1[++res1]=q[i];
        else q[i].cur+=temp,q2[++res2]=q[i];
    }
    for(int i=l;i<=mid;i++)
        if(p[i].l<=p[i].r)adjust(p[i].l,p[i].r,-p[i].a);
        else adjust(p[i].l,m,-p[i].a),adjust(1,p[i].r,-p[i].a);
    for(int i=1;i<=res1;i++)q[head+i-1]=q1[i];
    for(int i=1;i<=res2;i++)q[head+res1+i-1]=q2[i];
    divide(head,head+res1-1,l,mid);
    divide(head+res1,tail,mid+1,r);
}
int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        init();
        for(int i=1;i<=m;i++)
        {
            int x;
            scanf("%d",&x);
            next[i]=fir[x],fir[x]=i;
        }
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&q[i].need);
            q[i].id=i,q[i].cur=0;
        }
        scanf("%d",&k);
        for(int i=1;i<=k;i++)
            scanf("%d%d%d",&p[i].l,&p[i].r,&p[i].a);
        p[++k].l=1,p[k].r=m,p[k].a=INF;
        divide(1,n,1,k);
        for(int i=1;i<=n;i++)
            if(ans[i]==k)printf("NIE\n");
            else printf("%d\n",ans[i]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值