1. 程式人生 > >與或 【線段樹】 *

與或 【線段樹】 *

與或

這裡寫圖片描述

樣例:

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;
}