描述
构造一个长度为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');
}