“東信杯”廣西大學第一屆程式設計競賽(同步賽)J-RMQ(線段樹+位運算)
阿新 • • 發佈:2018-12-03
思路來源
https://ac.nowcoder.com/acm/contest/view-submission?submissionId=37515332
程式碼
#include<cstdio> #include<cstring> using namespace std; typedef long long ll; const int maxn=200020, maxsz=800020; //yh為lazy標記 //sum為節點區間和,其實沒必要維護,每次都要統計sigma(cs[i]<<i)的話會退化,查詢的時候現加即可 //cs[i]代表該節點所代表[l,r]區間中,第i位為1的個數。 struct pj{ll yh, sum, cs[20];} tr[maxsz]; ll a[maxn], ans; void updata(int x) { for (int i=0; i<20; ++i) tr[x].cs[i]=tr[x<<1].cs[i]+tr[x<<1|1].cs[i]; } void get_new(int x, int l, int r, int z) { //要或的z的右起第i位為1 for (int i=0; i<20; ++i) if(z>>i&1) tr[x].cs[i]=(r-l+1); } void push_down(int x, int l, int r) { if(!tr[x].yh) return; int mid=(l+r)>>1; get_new(x<<1, l, mid, tr[x].yh); get_new(x<<1|1, mid+1, r, tr[x].yh); //下放lazy標記,同時該節點標記清零 tr[x<<1].yh|=tr[x].yh; tr[x<<1|1].yh|=tr[x].yh; tr[x].yh=0; } void build(int x, int l, int r) { if(l==r) { for (int i=0; i<20; ++i) tr[x].cs[i]=a[l]>>i&1; return; } int mid=(l+r)>>1; build(x<<1, l, mid); build(x<<1|1, mid+1, r); updata(x);//即pushup操作 } void change(int x, int l, int r, int l0, int r0, int z) { if(l0<=l&&r<=r0)//[l,r]完全包含在要更新的[l0,r0]區間內,沒有必要下溯了 { get_new(x, l, r, z); tr[x].yh|=z; return; } //[l,r]與[l0,r0]相交,勢必用到[l,r]內的值,未更新的[l,r]不能用所以要下推 push_down(x, l, r); int mid=(l+r)>>1; //若l0<=mid,即[l,mid]包含l0;同理若r0>mid,即[mid+1,r]包含r0; //這說明在子區間裡有需要更新的相交段 //由於[l,r]初始是[1,n]所以根本不會進一個與[l0,r0]完全不相交的區間 if(l0<=mid) change(x<<1, l, mid, l0, r0, z); if(r0>mid) change(x<<1|1, mid+1, r, l0, r0, z); updata(x); } //同上,很好理解了QAQ void get_ans(int x, int l, int r, int l0, int r0) { if(l0<=l&&r<=r0) { for (int i=0; i<20; ++i) ans+=(tr[x].cs[i]<<i); return; } push_down(x, l, r); int mid=(l+r)>>1; if(l0<=mid) get_ans(x<<1, l, mid, l0, r0); if(r0>mid) get_ans(x<<1|1, mid+1, r, l0, r0); } int main() { int n, m; scanf("%d%d", &n, &m); for (int i=1; i<=n; ++i) scanf("%lld", a+i); build(1, 1, n); ll z; char s[100]; for (int ty, x, y; m--; ) { scanf("%s%d%d", s, &x, &y); if(s[0]=='S') { ans=0; get_ans(1, 1, n, x, y); printf("%lld\n", ans); } else { scanf("%lld", &z); change(1, 1, n, x, y, z); } } return 0; }
心得
感謝高中生的程式碼.jpg
通俗易懂好評,以後要多總結線段樹的板子,
平時要多練多總結,線段樹板子背不下來沒關係,
但是要懂得如何更新、如何打標記的精髓。
筆記都寫在程式碼裡了,回頭多整理幾道線段樹的題吧。
感覺自己線上段樹方面還是不熟練,
有的時候是更新一些值時,沒有認識到其對區間的影響可以被抽象出來。
這題就是完成一個或操作,按二進位制位儲存,開二十棵線段樹,
或者按程式碼,仍然按4*n建一棵樹,開20位存,
和x進行或操作時,如果x在第i位上是1,就將第i位在[l,r]賦值為1,
具體操作就是把代表區間的那個節點的值賦為區間長度即r-l+1。
求和的時候按位求和即可。
lazy標記的時候,若該區間先或上x1,再或上x2,其等價於或上(x1|x2),
所以標記就不斷的或就可以,下放的時候清零。
其實是自己記憶力太差背不下來程式碼
而且有過兩天不見程式碼就改(要)不(重)動(學)板子的傾向
不然才不會寫這麼多辣雞註釋