與或 【線段樹】 *
與或
樣例:
Input:
5 8
1 3 2 5 4
3 1 3
2 1 1 5
3 1 3
1 1 4 6
2 3 4 1
3 2 3
2 2 3 4
3 1 5
Output:
3
5
3
7
看到題目的時候相當僵硬,然後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的二進位制就好了
#include<bits/stdc++.h>
using namespace std;
#define N 200010
#define INF ((1<<20)-1)
#define LD (t<<1)
#define RD ((t<<1)|1)
struct Segment_Tree{
//1->& 2->|
int num[N],l[N<<2],r[N<<2];
int maxn[N<<2],sam[N<<2];
void pushup(int t){
sam[t]=(sam[LD]&sam[RD])&(INF^(maxn[LD]^maxn[RD]));
maxn[t]=max(maxn[LD],maxn[RD]);
}
void pushnow(int t,int v1,int v2){
sam[t]|=v1;
maxn[t]=(maxn[t]&(INF^v1))|(v1&v2);
}
void pushdown(int t){
pushnow(LD,sam[t],maxn[t]);
pushnow(RD,sam[t],maxn[t]);
}
void build(int t,int ll,int rr){
if(ll>rr)return;
l[t]=ll;r[t]=rr;
if(ll==rr){
maxn[t]=num[ll];
sam[t]=INF;
return;
}
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){
if(l[t]>rr||r[t]<ll)return;
if(ll<=l[t]&&r[t]<=rr){
if(l[t]==r[t]||((v1&sam[t])==v1)){
maxn[t]=(maxn[t]&(INF^v1))|(v1&v2);
return;
}
}
pushdown(t);
modify(LD,ll,rr,v1,v2);
modify(RD,ll,rr,v1,v2);
pushup(t);
}
int query(int t,int ll,int rr){
if(l[t]>rr||r[t]<ll)return 0;
if(ll<=l[t]&&r[t]<=rr)return maxn[t];
pushdown(t);
return max(query(LD,ll,rr),query(RD,ll,rr));
}
}tree;
int main(){
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&tree.num[i]);
tree.build(1,1,n);
for(int i=1;i<=m;i++){
int op,l,r,vl;
scanf("%d%d%d",&op,&l,&r);
if(op==1){
scanf("%d",&vl);
tree.modify(1,l,r,vl^INF,0);
}else if(op==2){
scanf("%d",&vl);
tree.modify(1,l,r,vl,vl);
}else printf("%d\n",tree.query(1,l,r));
}
return 0;
}
更新:
身為一個不顧一切強行剛的OIER,我肯定是會剛題剛到凌晨的,然後我就把它剛出來了,我的做法將&和|的操作分別進行,在modify過程中肯定是很好理解的,現在解釋一下對&和|的邊界設定:
定義check1檢查對&的modify:{
我們發現當vl涉及的數碼為0的所有二進位制位在取件sam中都為1的時候,那麼vl對於區間中的所有二進位制的這幾位的影響相同,不會改變大小順序,所以可以直接進行maxn的更新,條件:((INF^vl)&sam[t])==(INF^vl)
}
定義check2檢查對|的modify:{
我們發現當vl涉及的數碼為1的所有二進位制位在取件sam中都為1的時候,那麼vl對於區間中的所有二進位制的這幾位的影響相同,不會改變大小順序,所以可以直接進行maxn的更新,條件:(vl&sam[t])==vl
}
然後注意這個時候需要在pushdown的時候進行邊界條件判定,不然會出現奇奇怪怪的錯誤
附上程式碼:
#include<bits/stdc++.h>
using namespace std;
#define INF ((1<<20)-1)
#define N 200010
#define LD (t<<1)
#define RD ((t<<1)|1)
struct Segment_Tree{
//1->& 2->|
int num[N],sam[N<<2],maxn[N<<2],l[N<<2],r[N<<2];
void pushup(int t){
sam[t]=(sam[LD]&sam[RD])&(INF^(maxn[LD]^maxn[RD]));
maxn[t]=max(maxn[LD],maxn[RD]);
}
void pushnow(int t,int v1,int v2){
sam[t]|=v1;
maxn[t]=(maxn[t]&(INF^v1))|(v1&v2);
}
void pushdown(int t){
if(l[t]==r[t])return;
pushnow(LD,sam[t],maxn[t]);
pushnow(RD,sam[t],maxn[t]);
}
void build(int t,int ll,int rr){
if(ll>rr)return;
l[t]=ll;r[t]=rr;
if(ll==rr){sam[t]=INF;maxn[t]=num[ll];return;}
int mid=(ll+rr)>>1;
build(LD,ll,mid);
build(RD,mid+1,rr);
pushup(t);
}
bool check1(int t,int vl){return ((INF^vl)&sam[t])==(INF^vl);}
bool check2(int t,int vl){return (vl&sam[t])==vl;}
void modify1(int t,int ql,int qr,int vl){
if(r[t]<ql||qr<l[t])return;
pushdown(t);
if(ql<=l[t]&&r[t]<=qr)
if(l[t]==r[t]||check1(t,vl)){maxn[t]&=vl;return;}
modify1(LD,ql,qr,vl);
modify1(RD,ql,qr,vl);
pushup(t);
}
void modify2(int t,int ql,int qr,int vl){
if(r[t]<ql||qr<l[t])return;
pushdown(t);
if(ql<=l[t]&&r[t]<=qr)
if(l[t]==r[t]||check2(t,vl)){maxn[t]|=vl;return;}
modify2(LD,ql,qr,vl);
modify2(RD,ql,qr,vl);
pushup(t);
}
int query(int t,int ql,int qr){
if(r[t]<ql||qr<l[t])return 0;
if(ql<=l[t]&&r[t]<=qr)return maxn[t];
pushdown(t);
return max(query(LD,ql,qr),query(RD,ql,qr));
}
}tree;
int main(){
int n,m;scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&tree.num[i]);
tree.build(1,1,n);
for(int i=1;i<=m;i++){
int op,l,r,vl;
scanf("%d%d%d",&op,&l,&r);
if(op==1){
scanf("%d",&vl);
tree.modify1(1,l,r,vl);
}else if(op==2){
scanf("%d",&vl);
tree.modify2(1,l,r,vl);
}else printf("%d\n",tree.query(1,l,r));
}
return 0;
}