题目
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
*/