[BZOJ4571][SCOI2016]美味(二分+主席树)

可以利用二分的思想,具体步骤,就是从高到低确定每一个二进制位的数值。
可以看出,如果当前已经确定了前几位,那么这个解的范围一定是一个连续的区间。举个例子,假设初始解的枚举范围是 [0,262143] (即 18 位二进制数),如果当前已经确定了第 18,17,16 位分别为 1,0,1 ,那么这时候解的范围就是 [163840,196607]
设当前解的范围是 [s,t] ,第 i+1 位到第 18 位已经被确定,现在考虑怎样确定第 i 位,也就是说判断要把解的范围缩小为[s,s+t2]还是缩小为 [s+t2+1,t] 。由于要取最大值,所以先尝试可否把解的范围缩小到 [s+t2+1,t] ,下面令 L=s+t2+1,R=t
然后可以继续发现, [L,R] 内的数被 xor b 之后得到的值域,仍然是一段连续的区间。这时候,由于a xor b=c时一定有 a xor c=b ,所以将 L R i 位到第18分别与 b i位到第 18 位进行异或,就能得到一个新的区间 [u,v] ,也就是说 [u,v] 里的数被 xor b 之后得到的值域为[L,R]
可以得出:
u=L xor b2i12i1
v=R xor b2i12i1+2i11
这时候就可以看出,所有美味度为 [L,R] 的菜,都是评价值在区间 [ux,vx] 内的菜。如果在给定的可选择菜品区间内,存在一道菜的评价值在区间 [ux,vx] 内,那么就可以把答案区间调整为 [s+t2+1,t] ,否则调整为 [s,s+t2]
对于询问一个可选择菜品区间内评价值在区间 [ux,vx] 内的菜的个数,可以用树状数组离线询问。但是由于询问之间有联系,所以用主席树代替树状数组,以达到在线查询的目的。
代码:

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 2e5 + 5, NLogN = 7e6 + 5;
struct cyx {
    int lc, rc, cnt;
} T[NLogN];
int n, m, a[N], QAQ, rt[N];
void ins(int y, int &x, int l, int r, int p) {
    T[x = ++QAQ] = T[y]; T[x].cnt++; int mid = l + r >> 1;
    if (l == r) return; if (p <= mid) ins(T[y].lc, T[x].lc, l, mid, p);
    else ins(T[y].rc, T[x].rc, mid + 1, r, p);
}
int query(int l, int r, int s, int e, int p1, int p2) {
    if (l == s && r == e) return T[p2].cnt - T[p1].cnt;
    int mid = l + r >> 1;
    if (e <= mid) return query(l, mid, s, e, T[p1].lc, T[p2].lc);
    else if (s >= mid + 1) return query(mid + 1, r, s, e, T[p1].rc, T[p2].rc);
    else return query(l, mid, s, mid, T[p1].lc, T[p2].lc) +
        query(mid + 1, r, mid + 1, e, T[p1].rc, T[p2].rc);
}
void init() {
    int i; for (i = 1; i <= n; i++)
        ins(rt[i - 1], rt[i], 0, 131071, a[i]);
}
int main() {
    int i, b, x, l, r, ans; n = read(); m = read();
    for (i = 1; i <= n; i++) a[i] = read(); init();
    while (m--) {
        b = read(); x = read(); l = read(); r = read();
        ans = 0; for (i = 17; i >= 0; i--) {
            int tl = ans | (1 << i), tr = tl + (1 << i) - 1;
            tl ^= b; tr ^= b; tl = (tl >> i) << i; tr = (tr >> i) << i;
            tr += (1 << i) - 1; tl -= x; tr -= x;
            if (tr < 0 || tl > 131071) continue;
            if (tl < 0) tl = 0; if (tr > 131071) tr = 131071;
            if (query(0, 131071, tl, tr, rt[l - 1], rt[r]))
                ans = ans | (1 << i);
        }
        printf("%d\n", ans);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值