kaiser終於成為冒險協會的一員,這次冒險協會派他去冒險,他來到一處古墓,卻被大門上的守護神擋住了去路,守護神給出了乙個問題,
只有答對了問題才能進入,守護神給出了乙個自然數序列a,每次有一下三種操作。
1,給出l,r,x,將序列l,r之間的所有數都 and x
2,給出l,r,x,將序列l,r之間的所有數都 or x
3,給出l,r,詢問l,r之間的最大值
第一行包含兩個整數 n,m 接下來一行包含 n 個整數, 表示a序列,接下來 m 行, 每行描述了乙個操作.
2<=n<=2e5 2<=q<=2e5,0<=ai<=2^20.
對於每個第三類詢問, 輸出乙個數字.
3 59 19 0
3 2 3
2 3 3 18
1 2 2 10
3 1 2
1 1 3 11199
看到題目的時候相當僵硬,然後yy了乙個演算法然後僵硬了幾個小時最後gg,我自己的錯誤演算法還是不在這裡說了。。說多了都是淚
重大更新,我的**終於調出來了,比正解更好理解!!!在正解後給予解釋!!!!
正解如下:
首先我們可以發現,與和或的操作乙個是有0變成0,乙個是有1變成1
那麼如果乙個數與、或上另乙個數vl,只跟另乙個數的二進位制位上為0、1的位有關
我們考慮在什麼情境之下我們可以通過更新的vl值獲得我們需要的最大值maxn,既然是區間操作,我們不難想到用線段樹進行求解,但是單單是vl,我們並不能得到想要的maxn,所以我們需要維護其他的輔助變數
我們發現&和|操作所關聯的二進位制位只有vl上的0或者1,所以我們可以考慮定義線段樹上區間的sam,如果乙個區間的所有數在第i個二進位制位上的數碼相等,sam的這個二進位制位上的數為1,否則為0
通過更新sam,我們是可以很方便的計算和更新maxn的
但是當乙個區間存在多個不同的sam怎麼辦?我們用vl值進行更新的時候依然不能很方便的進行計算,所以我們定義check函式來限制線段樹修改的邊界問題,顯然,當滿足所有(&vl且vl上為0的位sam上為1)或者(|vl且vl上為1的位sam上為1)我們只需要用vl&、|上當前區間最大值就好,不滿足就向下遞迴問題
現在我們考慮向上維護sam值,顯然,對於乙個二進位制位,只有當左右兩區間的sam在這個二進位製上的值都為1且左右兩區間的數在這兩個二進位制位上相等才行,可以表示成sam[t]=(sam[ld] & sam[rd]) & (inf ^ maxn[ld] ^ maxn[rd]),這裡inf定義為二進位製上的極大值((1<<20)-1),然後maxn直接左右區間選擇max就好了
那麼如何向下更新呢?正解的思路真的比較神奇
我們把|操作強行通過某種等價的方式轉化成&操作,然後只用乙個修改函式進行修改,這個有興趣的照著**列舉二進位制情境驗證一下吧。
其次,還有乙個比較玄學的是正解沒有加lazy標記,直接用父親節點t的maxn和sam值對兒子節點s的maxn和sam值進行更新,這個稍微講一下,因為所有在sam[t]上出現的1一定會在sam[s]上出現,所以sam[s]|=sam[t]就可以維護,然後對於maxn,我們先把maxn[s]上左右和sam[t]有關的二進位制位全部變成零,然後再或上sam[t]和maxn[t]均為1的二進位制就好了
#includeusing namespace std;
#define n 200010
#define inf ((1<<20)-1)
#define ld (t<<1)
#define rd ((t<<1)|1)
struct segment_tree
void pushnow(int t,int v1,int v2)
void pushdown(int t)
void build(int t,int ll,int rr)
int mid=(ll+rr)>>1;
build(ld,ll,mid);
build(rd,mid+1,rr);
pushup(t);
} void modify(int t,int ll,int rr,int v1,int v2)
定義check2檢查對|的modify:
然後注意這個時候需要在pushdown的時候進行邊界條件判定,不然會出現奇奇怪怪的錯誤
附上**:
#includeusing namespace std;
#define inf ((1<<20)-1)
#define n 200010
#define ld (t<<1)
#define rd ((t<<1)|1)
struct segment_tree
void pushnow(int t,int v1,int v2)
void pushdown(int t)
void build(int t,int ll,int rr)
int mid=(ll+rr)>>1;
build(ld,ll,mid);
build(rd,mid+1,rr);
pushup(t);
} bool check1(int t,int vl)
bool check2(int t,int vl)
void modify1(int t,int ql,int qr,int vl)
modify1(ld,ql,qr,vl);
modify1(rd,ql,qr,vl);
pushup(t);
} void modify2(int t,int ql,int qr,int vl)
modify2(ld,ql,qr,vl);
modify2(rd,ql,qr,vl);
pushup(t);
} int query(int t,int ql,int qr)
}tree;
int main()else if(op==2)else printf("%d\n",tree.query(1,l,r));
} return 0;
}
bzoj5312 冒險 線段樹
kaiser終於成為冒險協會的一員,這次冒險協會派他去冒險,他來到一處古墓,卻被大門上的守護神擋住了去路,守護神給出了乙個問題,只有答對了問題才能進入,守護神給出了乙個自然數序列a,每次有一下三種操作。1,給出l,r,x,將序列l,r之間的所有數都 and x 2,給出l,r,x,將序列l,r之間的...
bzoj5312 冒險 勢能均攤線段樹
bzoj5312 冒險 如果一次操作對區間 和 區間 產生的影響是相同的,那麼該操作對整個區間的影響都是相同的 對於每次操作,在某些位上的值,對於整個區間影響是相同的,對相同影響的操作直接打標記 否則遞迴子樹 複雜度證明 include includeinline int read while c ...
BZOJ5312 冒險 勢能均攤線段樹
題目鏈結 這玩意兒是聽shadowice說的,好像很厲害的樣子 我們維護出區間 區間 區間最大值 結論 如果一次操作對區間 和 區間 產生的影響是相同的,那麼該操作對整個區間的影響都是相同的 證明可以看這裡 然後就做完了。時間複雜度 o nklogn k 是二進位制位數,這裡是20 include ...