西安郵電大學第五屆ACM-ICPC校賽 D.最簡單的一道題(線段樹技巧)

題目連接
思路:一開始看到這題毫無思路,賽後看了題解和通過的代碼,才瞭解到這個題的技巧。
顯然修改操作可以通過線段樹打標記實現。那麼難的就是查詢操作了。
考慮一種暴力的查詢:每次詢問的時候,我們把區間定在[x,n]上,然後類似dfs的一個過程在線段樹上搜,每次搜到葉子節點的時候 ,我們判斷[x,now]的最小值是不是等於now位置上的值。那這個複雜度顯然是不行的。
那我們考慮怎樣優化這個過程:
我們每次查詢的時候都會遞歸左右子樹,那有沒有一種只需遞歸一邊子樹的方法呢?
顯然是有的。
我們用一個數組g來記錄一段區間[l,r]中的x數量,其中x滿足:
1.l<=x<=r
2:t[x]=min(l~r)
顯然葉子節點的g值爲1,合併兩個子樹的時候顯然左子樹的答案是正確的,我們只需要另外計算一下右子樹的貢獻就行。
我們查詢的時候,需要保證合法的查詢區間的右端點是遞增的,那麼就要先遞歸左兒子再遞歸右兒子,然後我們維護一下前綴區間的最小值val,就可以計算這個區間的貢獻了。
區間貢獻計算方式:
顯然如果當前點爲葉子節點,那麼如果這個點的值小於等於val的時候貢獻爲1,否則爲0。
然後我們看這個區間的最小值如果不是小於等於val那麼這個區間顯然是不合法的,貢獻必然爲0。
反正我們看這個區間的左子樹的最小值是不是小於等於val的,如果左子樹的最小值小於等於val,那麼我們只需要遞歸處理左子樹,並加上右子樹的貢獻即可,因爲此時右子樹的g值必然是合法的,但是左子樹的g值不一定合法;如果左子樹的最小值不是小於等於val的我們就遞歸右子樹即可,因爲此時的左子樹的貢獻必然爲0。
這個方法很巧妙的維護了一個g數組,來做到查詢一個區間的貢獻的時候只花費了log的時間,總的查詢時間就是log2log^2.

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 10;
#define fi first
#define se second
#define pb push_back
int n,m;
int a[N];
#define mid (l+r>>1)
#define ls o<<1
#define rs o<<1|1
int t[N<<2],laz[N<<2];
int g[N<<2];
void pd(int o,int l,int r){
  if(laz[o]){
    laz[ls]+=laz[o];
    laz[rs]+=laz[o];
    t[ls]+=laz[o];
    t[rs]+=laz[o];
    laz[o]=0;
  }
}
int merge(int o,int  l,int r,int x){
  //找到這個區間
  if(l==r)return t[o]<=x;
  pd(o,l,r);
  if(t[o]>x)return 0;
  if(t[ls]>x)return merge(rs,mid+1,r,x);
  return merge(ls,l,mid,x)+g[o]-g[ls];
}
void push_up(int o,int l,int r){
  t[o]=min(t[ls],t[rs]);
  g[o]=g[ls]+merge(rs,mid+1,r,t[ls]);
}
void build(int o,int l,int r){
  if(l==r){
    t[o]=a[l];
    g[o]=1;
    return ;
  }
  build(ls,l,mid);
  build(rs,mid+1,r);
  push_up(o,l,r);
}

void up(int o,int l,int r,int x,int y,int d){
  if(l>=x&&r<=y){
    laz[o]+=d;
    t[o]+=d;
    return ;
  }
  pd(o,l,r);
  if(x<=mid)up(ls,l,mid,x,y,d);
  if(y>mid)up(rs,mid+1,r,x,y,d);
  push_up(o,l,r);
}
int getx(int o,int l,int r,int x){
  if(l==r)return t[o];
  pd(o,l,r);
  int ans;
  if(x<=mid)ans=getx(ls,l,mid,x);
  else ans=getx(rs,mid+1,r,x);
  push_up(o,l,r);
  return ans;
}
int get(int o,int l,int r,int x,int y,int &val){
  if(l>=x&&r<=y){
    int ans=merge(o,l,r,val);
    val=min(val,t[o]);
    return ans;
  }
  pd(o,l,r);
  int ans=0;
  if(x<=mid)ans+=get(ls,l,mid,x,y,val);
  if(y>mid)ans+=get(rs,mid+1,r,x,y,val);
  push_up(o,l,r);
  return ans;
}
int main() {
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++)scanf("%d",a+i);
  build(1,1,n);
  for(int i=1;i<=m;i++){
    char x;
    scanf(" %c",&x);
    if(x=='c'){
      int l,r,k;
      scanf("%d%d%d",&l,&r,&k);
      up(1,1,n,l,r,k);
    }else{
      int x;
      scanf("%d",&x);
      int d=getx(1,1,n,x);
      if(x+1<=n)printf("%d\n",get(1,1,n,x+1,n,d));
      else puts("0");
    }
  }
  return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章