BZOJ5312 冒險 線段樹 位運算

2022-05-24 23:09:11 字數 2963 閱讀 2471

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 ...