整体二分例题:POI2011Meteors——Chemist

题目地址:https://www.luogu.org/problemnew/show/P3527#sub

首先这个答案不是操作几次下了几场陨石雨之后的陨石个数,无法在线做,考虑离线做法。暴力的想法就是枚举每一场陨石雨,然后区间修改,每次判断没个国家的收集到的陨石个数是否大于等于需要的个数,枚举每一场陨石雨是O(k)的,区间修改是O(logm)的,枚举每一个国家是O(n)的,总时间复杂度是O(k*logm+k*n),显然超时。

我们知道时间复杂度的瓶颈主要是在每次枚举下到几场陨石雨时都需要扫一遍国家来判断,这可以用二分来做,而且这两部分都需要二分,于是就用整体二分来做。

首先我们要先二分答案,也就是第几场陨石雨,然后我们需要将所有的国家分为两部分,一部分是在这个二分的答案场陨石雨之前就可以收集到需要的陨石的国家,一部分是不能的国家。一旦我们找到一个精确的答案,也就是当二分的边界l==r时,就将这一次二分的L~R内的所有国家的答案设为这个答案。然后在具体判断时,每二分到一个答案,就将当前下的陨石雨的次数恢复到这个时刻,可以提前在1~m的范围内(也就是整个可能下陨石雨的范围内)建立树状数组,然后用树状数组实现,当当前的陨石雨的次数超过ans次时就一直减到ans次,反之同理。我们在一开始需要记录下每个国家所拥有的陨石收集器的位置,每次扫一煸这些位置,将它们各自收集到的陨石个数加起来,如果超过需要的那么这个答案一定可以,就往前面找,否则就不可以,往后找。但是需要注意的是每次需要更新往前找和往后找的国家的范围。而且由于此题是环的形式,所以需要分情况讨论:1.l<=r直接修改l~r。2.l>r修改l~m和1~r。

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int read()
{
    int ans=0;
    char ch=getchar(),last=' ';
    while(ch<'0'||ch>'9')
    {last=ch;ch=getchar();}
    while(ch>='0'&&ch<='9')
    {ans=ans*10+ch-'0';ch=getchar();}
    if(last=='-')ans=-ans;
    return ans;
}
const int M=300010,inf=1e9+7;
vector<int>pos[M];
//pos[i]中储存第i个国家有的陨石收集器的位置 
int n,m,k,num=0;
int p[M],id[M],ans[M],tmp[M]; 
int l[M],r[M],val[M];//每场陨石场发生的区间和数量
bool ok[M];
ll c[M];//树状数组
int lowbit(int x)
{
    return x&(-x);
}
ll sum(int x)
{
    ll ans=0;
    while(x){
        ans+=c[x];
        x-=lowbit(x);
    }
    return ans;
} 
void add(int x,int d)
{
    while(x<=m){
        c[x]+=d;
        x+=lowbit(x);
    }
}
void init()
{
    n=read();m=read();
    for(int i=1;i<=m;i++){
        int x=read();
        pos[x].push_back(i);
    }
    for(int i=1;i<=n;i++)
     p[i]=read();
    k=read();
    for(int i=1;i<=k;i++){
        l[i]=read();r[i]=read();
        val[i]=read();
    }
    k++;
    l[k]=1;r[k]=m;val[k]=inf;
    for(int i=1;i<=n;i++)
     id[i]=i;
}
void doit(int x,int d)
{
    if(l[x]<=r[x]){
        add(l[x],val[x]*d);
        add(r[x]+1,val[x]*(-d));
    }
    //如果l<r直接修改这段区间 
    else{
        add(1,val[x]*d);
        add(r[x]+1,val[x]*(-d));
        add(l[x],val[x]*d);
    }
    //如果l>r就修改1~r,l~m 
}
void solve(int L,int R,int l,int r)
//L到R是答案处在l~r这一段区间的国家,l到r二分答案
{
    if(L>R)return;
    if(l==r)//如果找到答案,这一段区间的答案全部是这个答案 
    {
        for(int i=L;i<=R;i++)
         ans[id[i]]=l;
        return;
    } 
    int mid=(l+r)>>1;
    while(num+1<=mid)doit(++num,1);
    while(num>mid)doit(num--,-1);
    int cnt=0,x;
    ll tot; 
    for(int i=L;i<=R;i++)
    {
        tot=0;x=id[i];
        int len=pos[x].size();
        //len为这个国家陨石收集器的个数
        for(int j=0;j<len;j++){
            tot+=sum(pos[x][j]);
            if(tot>=p[x])break;
            //如果已经收集到超过p[x]的陨石直接跳出 
        }
        if(tot>=p[x])ok[x]=1,cnt++;
        else ok[x]=0;
    }
    int l1=L,l2=L+cnt;
    for(int i=L;i<=R;i++){
        if(ok[id[i]])tmp[l1++]=id[i];
        else tmp[l2++]=id[i];
    }
    for(int i=L;i<=R;i++)
     id[i]=tmp[i];
    solve(L,l1-1,l,mid);
    solve(l1,l2-1,mid+1,r);
} 
int main()
{
    init();
    solve(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;
}

 

posted @ 2018-08-11 14:56  cellur925&Chemist  阅读(167)  评论(0编辑  收藏  举报