【線段樹與區間或】 woj 2820
阿新 • • 發佈:2018-11-10
描述
構造一個長度為n的非負整數序列x,滿足m個條件,第i個條件為x[li]|x[li+1]|…|x[ri]=pi。
輸入
第一行兩個整數n,m。接下來m行每行三個整數li,ri,pi。
輸出
如果存在這樣的序列x,第一行輸出Yes,第二行輸出n個不超過2^30-1的非負整數表示x[1]~x[n],否則輸出一行No。
樣例輸入[複製]
2 1
1 2 1
樣例輸出[複製]
Yes
1 1
提示
對於30%的資料,n,m<=1000。
對於另外30%的資料,pi<=1。
對於100%的資料,n,m<=100000,1<=li<=ri<=n,0<=pi<2^30。
如果拿到沒有什麼頭緒的題,可以先看一下部分資料,嘗試分析,然後推廣到100%的資料。
對於這道題,先看一下pi<=1的情況:
如果pi=0,那麼li到ri都必須是0,如果pi=1,那麼li到ri至少有一個1。我們可以先把這個序列全部變成1,然後把pi為0的區間賦成0,最後再判一下是否合法。可以用線段樹來維護這個序列,支援區間修改和區間查詢。時間複雜度O(mlogn)。
30分到手了。那麼考慮一下所有的情況。
我們注意到或運算它的每一位是獨立的!就相當於是 這個序列的二進位制下的每一位 都是一個pi<=1的問題。
可以先把xi的每一位都設成1:把x序列初始化成(1<<30-1)。對於每個條件,把x[li]...x[ri]中的pi=0的位置【二進位制下】設成0,最後判斷一下區間的 或 是否等於pi。同樣用線段樹維護。時間複雜度O(mlogn)。
#include<bits/stdc++.h> using namespace std; const int MAX=101000; int l[MAX],r[MAX],p[MAX],ans[MAX<<1]; int n,m,maxn=1; void read(int &x){ x=0;char ch=getchar(); while(!isdigit(ch)) ch=getchar(); while(isdigit(ch)) x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); } void add(int root,int l,int r,int L,int R,int C){ if(L<=l&&R>=r){ ans[root]&=C; return; } int mid=(l+r)>>1; if(L<=mid) add(root<<1,l,mid,L,R,C); if(R>mid) add(root<<1,mid+1,r,L,R,C); } int query(int root,int l,int r,int L,int R){ if(L<=l&&R>=r) return ans[root]; int mid=(l+r)>>1; int ret=0; if(L<=mid) ret|=query(root<<1,l,mid,L,R); if(R>mid) ret|=query(root<<1|1,mid+1,r,L,R); return ret; } int main(){ /* freopen("or.in","r",stdin); freopen("or.out","w",stdout);*/ read(n),read(m); for(;maxn<n;maxn<<=1); for(int i=1;i<maxn*2;i++) ans[i]=(1<<30)-1; for(int i=1;i<=m;++i){ read(l[i]),read(r[i]),read(p[i]); add(1,1,maxn,l[i],r[i],p[i]); } //add函式只會更改幾個結點的區間或,不會更新其子樹的或。 for(int i=1;i<maxn;++i){ ans[i<<1]&=ans[i]; ans[i<<1|1]&=ans[i]; } //從上往下把子樹區間的或更新一下。 for(int i=maxn-1;i>=1;--i) ans[i]=ans[i<<1]|ans[i<<1|1]; //從下往上把父親區間的或更新一下 for(int i=1;i<=m;++i){ if(query(1,1,maxn,l[i],r[i])!=p[i]){ printf("No\n"); return 0; } } printf("Yes\n"); for(int i=1;i<=n;++i) printf("%d ",ans[maxn+i-1]); //maxn是最後一層【葉節點】的第一個下標。 //ans[maxn]對應x[1]。從ans[maxn]開始的n個就是x[1]到x[n]。 putchar('\n'); }