H. AND = OR (线段树,思维)

题目

https://codeforces.com/gym/103102/problem/H

在这里插入图片描述
样例

input
5 15
0 1 1 3 2
1 1
1 2
1 3
1 4
1 5
2 2
2 3
2 4
2 5
3 3
3 4
3 5
4 4
4 5
5 5
output
NO
NO
YES
YES
YES
NO
YES
YES
YES
NO
NO
YES
NO
NO
NO

分析

  • 题目问能否将原数组分割为两个集合,其中一个的或和等于另一个的与和。
  • 考虑二进制下1的个数,会发现或其它数,1的个数不会减少,与其它数,1的个数不会增多。可以以此作为分割依据。
  • 假设有成功的分割方案,设两个相等的和为 x,二进制下有k个1。那么,或的那个集合中的数二进制下1的个数不可能大于k,与的那个集合中的数二进制下1的个数不可能小于k。而且,会发现区间内所有数中1的个数等于1的数应该是全相等的,不然无论怎么放,两边都不可能会等于一个相同的含k个1的数。
  • 那么对每个询问,只要枚举两边相等数二进制下的1的个数 k(0~30),查询区间内1的个数为 0–k-1的数的或和,以及1的个数为k+1–30的数的与和,至于刚好有k个1的数,看看是不是都相同,若相同,且可以分配使得最终两边满足相同,那么就找到了一个解。若对所有 k 都无解,那么就无解。
  • 对每个 k 建一个线段树,就可以维护区间信息了。
  • 具体实现中,恰好有 k 个的可以选择两边都放(前提是个数大于1)也可以全放一边,就这两大类。都验证一下就行了。
  • 时间复杂度,O(nqlog)

代码

#include <bits/stdc++.h>
using namespace std;


template<int maxn>
struct segment_tree {
#define LX(x) ((x)<<1)
#define RX(x) (((x)<<1)|1)

    struct node {
        int v_and;
        int v_or;
        int v_cnt;
        int l, r;
    };
    node a[(maxn<<3)+10];
    
    
    void build(int x, int l, int r) {
        a[x].l = l;
        a[x].r = r;
        if (l>=r) {
            a[x].v_and = -1;
            a[x].v_or = a[x].v_cnt = 0;
            return;
        }

        int lx = LX(x);
        int rx = RX(x);
        build(lx, l, (l+r)>>1);
        build(rx, ((l+r)>>1)+1, r);

        a[x].v_and = a[lx].v_and & a[rx].v_and;
        a[x].v_or = a[lx].v_or | a[rx].v_or;
        a[x].v_cnt = a[lx].v_cnt + a[rx].v_cnt;
    }

    void modify(int x, int pos, int val) {
        if (a[x].l>=a[x].r) {
            a[x].v_and = a[x].v_or = val;
            a[x].v_cnt = 1;
            return;
        }
        int lx = LX(x);
        int rx = RX(x);
        if (pos <= a[lx].r)
            modify(lx, pos, val);
        if (pos >= a[rx].l)
            modify(rx, pos, val);
        
        a[x].v_and = a[lx].v_and & a[rx].v_and;
        a[x].v_or = a[lx].v_or | a[rx].v_or;
        a[x].v_cnt = a[lx].v_cnt + a[rx].v_cnt;
    }

    int query_and(int x, int L, int R) {
        if (a[x].l>=L && a[x].r<=R) {
            return a[x].v_and;
        }
        int ret=-1;
        if (L<=a[LX(x)].r)
            ret &= query_and(LX(x), L, R);
        if (R>=a[RX(x)].l)
            ret &= query_and(RX(x), L, R);
        return ret;
    }

    int query_or(int x, int L, int R) {
        if (a[x].l>=L && a[x].r<=R) {
            return a[x].v_or;
        }
        int ret=0;
        if (L<=a[LX(x)].r)
            ret |= query_or(LX(x), L, R);
        if (R>=a[RX(x)].l)
            ret |= query_or(RX(x), L, R);
        return ret;
    }

    int query_cnt(int x, int L, int R) {
        if (a[x].l>=L && a[x].r<=R) {
            return a[x].v_cnt;
        }
        int ret=0;
        if (L<=a[LX(x)].r)
            ret += query_cnt(LX(x), L, R);
        if (R>=a[RX(x)].l)
            ret += query_cnt(RX(x), L, R);
        return ret;
    }

#undef LX
#undef RX    
};

#define MAXN 100005
int n, q;
int a[MAXN];
int cnt1[MAXN];
int tmp_and[MAXN];
int tmp_or[MAXN];
int tmp_cnt[MAXN];
segment_tree<MAXN> s[31];


int get_cnt1(int x)
{
    if (x<0) return 10086;
    int ret=0;
    for (; x; x>>=1)
        ret+=x&1;
    return ret;
}

void solve()
{
    int maxcnt1=0;
    scanf("%d%d", &n, &q);
    for (int i=0; i<=30; i++)
        s[i].build(1, 1, n);

    for (int i=1; i<=n; i++) {
        scanf("%d", &a[i]);
        cnt1[i] = get_cnt1(a[i]);
        maxcnt1=max(maxcnt1, cnt1[i]);

        s[cnt1[i]].modify(1, i, a[i]);
    }

    while (q--) {
        int ll, rr;
        scanf("%d%d", &ll, &rr);

        int ok=0;

        for (int i=0; i<=maxcnt1; i++) {
            tmp_or[i] = s[i].query_or(1, ll, rr);
            tmp_and[i] = s[i].query_and(1, ll, rr);
            tmp_cnt[i] = s[i].query_cnt(1, ll, rr);
        }

        for (int k=0; k<=maxcnt1 && !ok; k++) {
            int tmp;
            int A=0, cntA=0;
            int B=-1, cntB=0;
            int M, cntM;
            for (int i=0; i<k; i++) {
                tmp = tmp_or[i];
                A |= tmp;
                cntA += tmp_cnt[i];
            }
            for (int i=k+1; i<=maxcnt1; i++) {
                tmp = tmp_and[i];
                B &= tmp;
                cntB += tmp_cnt[i];
            }

            M = tmp_and[k];
            cntM = tmp_cnt[k];

            ok |= (A==(B&M) && cntA>0 && (cntB+cntM)>0);

            if (get_cnt1(M) != k) {
                continue;
            }

            if (cntM>1) ok |= ((A|M) == (B&M));

        }

        puts(ok ? "YES" : "NO");
    }
}



int main()
{
    solve();
    return 0;
}

/*

5 15  0 1 1 3 2 1 1 1 2 1 3 1 4 1 5 2 2 2 3 2 4 2 5 3 3 3 4 3 5 4 4 4 5 5 5


*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值