1. 程式人生 > >【洛谷P2801】教主的魔法【分塊】【二分】

【洛谷P2801】教主的魔法【分塊】【二分】

題目大意:

題目連結:https://www.luogu.org/problemnew/show/P2801
給出一串數列,有兩個操作:

  • M   x   y  
    z : M\ x\ y\ z:
    將位置在 x x y
    y
    之間的數全部加上 z z
  • A   x
      y   z : A\ x\ y\ z:
    查詢位置在 x x y y 之間有多少個數不小於 z z

思路:

30分做法:

暴力。

100分做法:

考慮用分塊。
對於每次修改操作,如果修改的位置都在一個區間,那麼就暴力修改。
如果不在同一個區間,那麼就將中間的區間 a d d add 陣列全部加上 z z ,其餘兩邊暴力。
修改很簡單吧。
那麼對於查詢操作,如果查詢的位置在同一個區間,暴力。
如果不在同一個區間,可以想到,如果這個區間是單調遞增的,那麼就可以用二分查詢,找到第一個比 z z 小的,那麼後面的全部比 z z 大(或等於)。
那麼就在每次修改之後維護另一個數組 a [ i ] a[i] ,保證每一個塊在 a a 陣列中都是單調遞增的。就可以了。


程式碼:

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cmath>
#define N 1000100
using namespace std;

int tall[N],pos[N],a[N],L[N],R[N],add[N];
int n,m,t;
char c;

void reset(int x)  //重構a陣列
{
    for (int i=L[x];i<=R[x];i++)
     a[i]=tall[i];
    sort(a+L[x],a+R[x]+1);
}

int sum(int l,int r,int z)  //暴力求個數
{
    int ans=0;
    for (int i=l;i<=r;i++)
     if (tall[i]>=z) ans++;
    return ans;
}

int find(int l,int r,int z)  //二分求個數
{
    int mid,p=r;
    while(l<=r)
    {
        mid=(l+r)/2;
        if(a[mid]<z) l=mid+1;
         else r=mid-1;
    }
    return p-l+1;
}

int ask(int l,int r,int z)
{
    int q=pos[l],p=pos[r];
    if (q==p) return sum(l,r,z-add[q]);  //每次z要減去add[這個塊],因為這個塊整體增加了add[i],所以查詢時要減去add[i]
    int ans=0;
    for (int i=q+1;i<p;i++)
     ans+=find(L[i],R[i],z-add[i]);
    return ans+sum(l,R[q],z-add[q])+sum(L[p],r,z-add[p]);
}

void change(int l,int r,int z)
{
    int q=pos[l],p=pos[r];
    if (q==p)
    {
        for (int i=l;i<=r;i++) tall[i]+=z;
        reset(q);
        return;
    }
    for (int i=l;i<=R[q];i++) tall[i]+=z;
    for (int i=L[p];i<=r;i++) tall[i]+=z;
    reset(q);
    reset(p);  //重構
    for (int i=q+1;i<p;i++)
     add[i]+=z;
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++) scanf("%d",&tall[i]);
    t=(int)sqrt(n);
    for (int i=1;i<=t;i++)
    {
        L[i]=R[i-1]+1;
        R[i]=i*t;
    }
    if (R[t]<n)
    {
        t++;
        L[t]=R[t-1]+1;
        R[t]=n;
    }
    for (int i=1;i<=t;i++)
    {
        for (int j=L[i];j<=R[i];j++)
      	 pos[j]=i;
        reset(i);
    } 
    int x,y,z;
    while (m--)
    {
        cin>>c;
        scanf("%d%d%d",&x,&y,&z);
        if (c=='A') printf("%d\n",ask(x,y,z));
         else change(x,y,z);
    }
    return 0;
}