线段树 ---- H. AND = OR (或和与的性质之1的个数 + 线段树)

题目链接


题目大意:

给你一个序列 { a } \{a\} {a},每次询问一个区间 [ l , r ] [l,r] [l,r]
问你这个区间里面的数是否可以分成两部分使得一部分的 A N D = AND= AND=另一部分的 O R OR OR


解题思路:

  1. 这个题思路很妙:
  2. 首先我们知道对于 & \& &操作数越多只会越 & \& &越小,而 ∣ | 就是相反的
  3. 那么我们可以假设这个区间里面的数可以分成两部分,那么假设最后的答案是 a n s ans ans
  4. 假设 a n s ans ans中有 x x x个1,那么对于区间里面数包含的1的个数大过 x x x的话那么一定是执行 & \& &操作,反过来同理小的就是 ∣ | 操作。
  5. 但是如果是刚好为 x x x怎么办?,它是 & \& &还是 ∣ | 呢?
  6. 这里还要再讨论一下:
  7. 如果刚好为 x x x的数是不同的?那么它们一定是要划分到一个集合里面(如果它们在不同的集合里面肯定结果不会相等)
  8. 如果 x x x的数都是一样的那么我们就可以划分到两边,也可以在一边,分成两边的时候你最起码个数要大于1

那么思路就很明显了:我们枚举 x ∈ [ 0 , 30 ] x\in[0,30] x[0,30],然后 [ 0 , x − 1 ] [0,x-1] [0,x1]里面的数求 ∣ | , [ x + 1 , 30 ] [x+1,30] [x+1,30]里面的数取 & \& &,那么对于 x x x我们要记录里面的数是否是同一个数。和数的个数

那么我们就可以开 30 30 30棵线段树。每个维护区间里面1个数为 x x x的各种信息。


AC code

#include <bits/stdc++.h>
#define mid ((l + r) >> 1)
#define Lson rt << 1, l , mid
#define Rson rt << 1|1, mid + 1, r
#define ms(a,al) memset(a,al,sizeof(a))
#define log2(a) log(a)/log(2)
#define lowbit(x) ((-x) & x)
#define IOS std::ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
#define INF 0x3f3f3f3f
#define LLF 0x3f3f3f3f3f3f3f3f
#define f first
#define s second
#define endl '\n'
using namespace std;
const int N = 2e6 + 10, mod = 1e9 + 9;
const int maxn = 800010;
const long double eps = 1e-5;
const int EPS = 500 * 500;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> PII;
typedef pair<ll,ll> PLL;
typedef pair<double,double> PDD;
template<typename T> void read(T &x) {
   x = 0;char ch = getchar();ll f = 1;
   while(!isdigit(ch)){if(ch == '-')f*=-1;ch=getchar();}
   while(isdigit(ch)){x = x*10+ch-48;ch=getchar();}x*=f;
}
template<typename T, typename... Args> void read(T &first, Args& ... args) {
   read(first);
   read(args...);
}
struct inf {
    int a, b, c;
}all;
struct Segtree {
    int OR[maxn];
    int AND[maxn];
    int is[maxn];// 区间里面的数是否是一种而已?
    int sum[maxn];//区间数额个数
    
    inline void pushup(int rt) {
        
        sum[rt] = sum[rt<<1] + sum[rt<<1|1];  

        if(!is[rt<<1]) is[rt] = is[rt<<1|1];
        else if(!is[rt<<1|1]) is[rt] = is[rt<<1];
        else if(is[rt<<1|1] != is[rt<<1]) is[rt] = -1;
        else is[rt] = is[rt<<1];

        OR[rt] = (OR[rt<<1] | OR[rt<<1|1]);
        AND[rt] = (AND[rt<<1] & AND[rt<<1|1]);
    }
    void build(int rt, int l, int r) {
        if(l == r) {
            OR[rt] = sum[rt] = is[rt] = 0;
            AND[rt] = (1<<30)-1;
            return;
        }
        build(Lson);
        build(Rson);
        pushup(rt);
    } 
    void insert(int rt, int l, int r, int pos, int val) {
        if(l == r) {
            sum[rt] = 1;
            is[rt] = OR[rt] = AND[rt] = val;
            return;
        }
        if(pos <= mid) insert(Lson,pos,val);
        else insert(Rson,pos,val);
        pushup(rt);
    }
    inline inf mege(inf a, inf b) {
        inf res;
        res.a = a.a & b.a;
        res.b = a.b | b.b;
        res.c = a.c + b.c;
        return res;
    }
    inf quaryAND_OR_SUM(int rt, int l, int r, int L, int R) {
        if(L <= l && R >= r) return (inf){AND[rt],OR[rt],sum[rt]};
        inf res = {(1<<31)-1,0,0};
        if(L <= mid) res = mege(quaryAND_OR_SUM(Lson,L,R),res);
        if(R > mid) res = mege(quaryAND_OR_SUM(Rson,L,R),res);
        return res;
    }

    int quaryis(int rt, int l, int r, int L, int R) {
        if(L <= l && R >= r) return is[rt];
        if(L > mid) return quaryis(Rson,L,R);
        else if(R <= mid) return quaryis(Lson,L,R);
        else {
          int lson = quaryis(Lson,L,R);
          int rson = quaryis(Rson,L,R);
          if(lson == -1 || rson == -1) return -1;
          if(!lson) return rson;
          if(!rson) return lson;
          if(lson == rson) return lson;
          return -1;
        }
    }

}sgt[40];
// n个点,q个询问,Less[i]是[0,i]里面所有数的OR,eq[i]是x=i时候的数的AND和OR
int n, q, AND[40], OR[40], arr[maxn], Less[40], more;
bool eqis[40], isOR[40], flag, isAND;
PII eq[40];
int main() {
    read(n,q);
    for(int i = 0; i <= 30; ++ i) sgt[i].build(1,1,n); //建线段树
    for(int i = 1; i <= n; ++ i) {
        read(arr[i]);
        int num = 0;
        for(int j = 30; j >= 0; j --) {// 获取每个数的1的个数
            if(arr[i] >> j & 1)
                num ++;
        }   
        sgt[num].insert(1,1,n,i,arr[i]); // 插到对应位置去
    } 
    
    while(q --) {
        int l, r;
        read(l,r);
        for(register int i = 0; i <= 30; ++ i) {// 枚举x
            inf is = sgt[i].quaryAND_OR_SUM(1,1,n,l,r);// 获取[l,r]区间里面的AND OR sum
            AND[i] = is.a;
            OR[i] = is.b;
            eqis[i] = is.c; // x=i是否存在
            isOR[i] = is.c; //是否OR过了也就是[0,i]是否有数
            if(i) isOR[i] |= isOR[i-1];
            (i != 0) ? (Less[i] = Less[i-1] | OR[i]) : (Less[i] = OR[i]); //维护前缀OR
            if(!is.c) continue;//如果下面没数了
            int _is = sgt[i].quaryis(1,1,n,l,r);
            eq[i] = {AND[i],OR[i]};//x = i时候数的 AND 和 OR
            if(is.c >= 2 && _is != -1) eq[i].first = -2;// 这里设置-2是为了判断那个x全是同一个数并且个数>= 2
        }

        flag = 0, isAND = 0; // flag是答案,isAND就是判断是否AND过了
        more = (1<<31)-1;
        for(register int i = 30; i >= 1 && !flag; -- i) {//枚举&的位置i-2, i-1 和 i
            more &= AND[i];
            isAND |= eqis[i];
            if(i == 1) break;
            if(!eqis[i-1]) { // 如果没有x=i-1看[0,i-2]和[i,30]是否相等
               if(more == Less[i-2] && isAND && isOR[i-2]) flag = 1;
               continue;
            }
            if(eq[i-1].first == -2) {
                if((Less[i-2]|eq[i-1].second) == (more&eq[i-1].second)) flag = 1;
                eq[i-1].first = eq[i-1].second; // 记得复原eq[i-1].first
            }
            if(isAND && (Less[i-2] | eq[i-1].second) == more) flag = 1;
            if(isOR[i-2] && (more & eq[i-1].first) == Less[i-2]) flag = 1;//分到AND和OR集合
        }
        
        if(eqis[30]) {//特判两个边界
            if(eq[30].first == -2) {
                eq[30].first = eq[30].second;
                if((Less[29]|eq[30].second) == eq[30].second) flag = 1;
            } 
            if(isOR[29] && Less[29] == eq[30].first) flag = 1;
        } 

        if(eqis[0]) {
            if(eq[0].first == -2) {
                if((more&eq[0].second) == eq[0].second) flag = 1;
            } 
            if(isAND && more == eq[0].second) flag = 1;
        }

        puts(flag ? "YES" : "NO"); 
    }
    return 0;
}
/*
5 1
1 1 1 1 1
1 2

7 1
4 2 9 6 2 4 2 
5 6

4 1
4 4 3 0 
2 3




*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值