【BZOJ2527】Meteors(POI2011)-整体二分+树状数组

测试地址:Meteors
题目大意:一个环上有 m m 个区域,有n个国家,每个国家管辖一些区域,有 k k 场陨石雨陆续降落,每场陨石雨会给一个环上的区间内的每个区域提供Ai颗陨石,现在每个国家都有采样任务,即要在他们管辖的区域中收集共 Pi P i 颗陨石,问每个国家最早在第几场陨石雨后就可以完成任务。
做法:本题需要用到整体二分+树状数组。
首先,如果只有一个询问,我们就可以用二分答案+树状数组统计来算出答案(区间加,单点修改可以差分后用树状数组维护),时间复杂度为 O(nlog2n) O ( n log 2 ⁡ n ) 。而这题有多个询问,又没有强制在线,因此考虑整体二分。
我们需要完成函数 solve(s,t,l,r) s o l v e ( s , t , l , r ) ,表示要处理的询问区间为 [s,t] [ s , t ] ,它们的答案已知在区间 [l,r] [ l , r ] 中。那么按照套路,我们令区间 [l,r] [ l , r ] 的中点为 mid m i d ,考虑在区间 [l,mid] [ l , m i d ] 的所有操作对询问的影响,这一步可以用树状数组完成,然后将所有询问进一步分治求解。我们来分析该算法的时间复杂度,显然所有操作的总时间复杂度是 O(nlog2n) O ( n log 2 ⁡ n ) 的,而对于一个询问,它会被计算 logn log ⁡ n 次,那么所有询问的总时间复杂度就是 O(nlog2n) O ( n log 2 ⁡ n ) 的,因此算法的总时间复杂度就是 O(nlog2n) O ( n log 2 ⁡ n ) ,可以通过此题。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,k,first[300010]={0},nxt[300010],ans[300010];
int L[300010],R[300010];
ll sum[300010]={0},A[300010];
struct query
{
    int id;
    ll limit,sum;
}q[300010],q1[300010],q2[300010];

int lowbit(int x)
{
    return x&(-x);
}

void add(int l,int r,ll c)
{
    for(int i=l;i<=m;i+=lowbit(i))
        sum[i]+=c;
    for(int i=r+1;i<=m;i+=lowbit(i))
        sum[i]-=c;
}

ll query(int x)
{
    ll ans=0;
    for(int i=x;i;i-=lowbit(i))
        ans+=sum[i];
    return ans;
}

void solve(int s,int t,int l,int r)
{
    if (s>t) return;
    if (l==r)
    {
        for(int i=s;i<=t;i++)
            ans[q[i].id]=l;
        return;
    }

    int mid=(l+r)>>1,tot1=0,tot2=0;
    for(int i=l;i<=mid;i++)
    {
        if (L[i]<=R[i]) add(L[i],R[i],A[i]);
        else add(1,R[i],A[i]),add(L[i],m,A[i]);
    }

    for(int i=s;i<=t;i++)
    {
        ll s=0;
        int now=first[q[i].id];
        while(now)
        {
            s+=query(now);
            if (q[i].sum+s>=q[i].limit) break;
            now=nxt[now];
        }
        if (q[i].sum+s>=q[i].limit) q1[++tot1]=q[i];
        else q[i].sum+=s,q2[++tot2]=q[i];
    }
    for(int i=1;i<=tot1;i++)
        q[s+i-1]=q1[i];
    for(int i=1;i<=tot2;i++)
        q[s+tot1+i-1]=q2[i];

    for(int i=l;i<=mid;i++)
    {
        if (L[i]<=R[i]) add(L[i],R[i],-A[i]);
        else add(1,R[i],-A[i]),add(L[i],m,-A[i]);
    }

    solve(s,s+tot1-1,l,mid);
    solve(s+tot1,t,mid+1,r);
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int x;
        scanf("%d",&x);
        nxt[i]=first[x];
        first[x]=i;
    }

    for(int i=1;i<=n;i++)
        scanf("%lld",&q[i].limit);
    scanf("%d",&k);
    for(int i=1;i<=k;i++)
        scanf("%d%d%lld",&L[i],&R[i],&A[i]);
    for(int i=1;i<=n;i++)
        q[i].id=i,q[i].sum=0;
    solve(1,n,1,k+1);
    for(int i=1;i<=n;i++)
    {
        if (ans[i]==k+1) 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、付费专栏及课程。

余额充值