洛谷题单指南-线段树的进阶用法-P3293 [SCOI2016] 美味

原题链接:https://www.luogu.com.cn/problem/P3293

题意解读:计算每位顾客i认为的[li,ri]之间菜品的最大美味值,美味值是bi^(aj+xi),bi、xi是与顾客有关的属性,aj是菜品的属性。

解题思路:要计算每位顾客的最大美味值bi^(aj+xi),是一个异或运算。

在学习trie树时,我们知道要使得异或值最大,最好二进制位的每一位都相反;

所以,可以按位来处理,对bi的每一个二进制位,如果存在aj+xi的对应位与之相反,则能确保结果最大,如果不存在相反位的,就取相同位的值。

对于每一个顾客,bi、xi都是确定的,问题转化为是否能在[li,ri]之间菜品查询到合适的aj,使得异或结果最大。

问题的关键在于,合适的aj意味着什么?

我们先来看最大异或结果的计算方式,设aj+xi的取值为tmp,初始tmp=0

从高到低遍历bi的二进制位,设已经处理到第k位(最低位k=0)

1、如果bi的第k位为0,显然aj+xi的第k位最好是1,剩下的位取值范围从0..0 ~ 1..1,如图所示

 二进制位 ...   k+2  k+1 k k-1 ... 1 0
 bi ... - - 0 x ... x x
 aj+xi最小值 ... - - 1 0 ... 0 0
 aj+xi最大值 ... - - 1 1 ... 1 1

aj+xi最小值应该是tmp + (1 << k),aj+xi最大值应该是tmp + (1 << k + 1) - 1,所以aj的范围应该是[tmp + (1 << k) - xi, tmp + (1 << k + 1) - 1 - xi]

只需要在[li,ri]之间菜品查询评价值在[tmp + 1 << k - xi, tmp + (1 << k + 1) - 1 - xi]的元素个数cnt,

  如果cnt > 0,  aj+xi的第k位可以为1,tmp = tmp + (1 << k)

  如果cnt == 0,aj+xi的第k位只能为0,tmp不变

2、如果如果bi的第k位为1,显然aj+xi的第k位最好是0,剩下的位取值范围从0..0 ~ 1..1,如图所示

 二进制位 ...   k+2  k+1 k k-1 ... 1 0
 bi ... - - 1 x ... x x
 aj+xi最小值 ... - - 0 0 ... 0 0
 aj+xi最大值 ... - - 0 1 ... 1 1

aj+xi最小值应该是tmp,aj+xi最大值应该是tmp + (1 << k) - 1,所以aj的范围应该是[tmp - xi, tmp + (1 << k) - 1 - xi]

只需要在[li,ri]之间菜品查询评价值在[tmp - xi, tmp + (1 << k) - 1 - xi]的元素个数cnt,

  如果cnt > 0,  aj+xi的第k位可以为0,tmp不变

  如果cnt == 0,aj+xi的第k位只能为1,tmp = tmp + (1 << k)

将最后得到的tmp与bi做异或即得最终最大美味值。

其中,关键操作在于在[li,ri]之间菜品查询评价值在[lv, rv]的元素个数cnt,根据前面学习得知,是主席树的典型应用场景,通过对所有菜品的评价值建立可持久化线段树即可。

100分代码:

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

const int N = 200005, M = 100005;

struct Node
{
    int L, R;
    int cnt;
} tr[N * 24];
int root[N], idx;
int a[N], b[M], x[M], l[N], r[N], maxa;
int n, m;

//将根为pre的线段树中值v的数量加1,返回新的根u,通过节点复制方式
int update(int pre, int l, int r, int v)
{
    int u = ++idx;
    tr[u].L = tr[pre].L;
    tr[u].R = tr[pre].R;
    tr[u].cnt = tr[pre].cnt + 1;
    if(l == r) return u;
    int mid = l + r >> 1;
    if(v <= mid) tr[u].L = update(tr[u].L, l, mid, v);
    else tr[u].R = update(tr[u].R, mid + 1, r, v);
    return u;
}

//在根为lu,ru的两棵线段树中查询值在[lv,rv]的元素数量
//用ru中的数量减去lu中的数量可得到外层查询约束中的l[i]~r[i]之间菜品
int query(int lu, int ru, int l, int r, int lv, int rv)
{
    if(l >= lv && r <= rv) return tr[ru].cnt - tr[lu].cnt;
    else if(l > rv || r < lv) return 0;
    else 
    {
        int mid = l + r >> 1;
        return query(tr[lu].L, tr[ru].L, l, mid, lv, rv) + query(tr[lu].R, tr[ru].R, mid + 1, r, lv, rv);
    }
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i];
        maxa = max(maxa, a[i]);
    } 
    for(int i = 1; i <= n; i++) root[i] = update(root[i - 1], 0, maxa, a[i]);
    for(int i = 1; i <= m; i++)
    {
        cin >> b[i] >> x[i] >> l[i] >> r[i];
        int tmp = 0;
        for(int k = 20; k >= 0; k--) //遍历b[i]的二进制位
        {
            if(b[i] & (1 << k)) //第k位为1
            {
                int lv = tmp - x[i], rv = tmp + (1 << k) - 1 - x[i];
                int cnt = query(root[l[i] - 1], root[r[i]], 0, maxa, lv, rv);
                if(cnt == 0) tmp += 1 << k;
            }
            else //第k位为0
            {
                int lv = tmp + (1 << k) - x[i], rv = tmp + (1 << k + 1) - 1 - x[i];
                int cnt = query(root[l[i] - 1], root[r[i]], 0, maxa, lv, rv);
                if(cnt > 0) tmp += 1 << k;
            }
        }
        int res = b[i] ^ tmp;
        cout << res << endl;
    }
    return 0;
}

 

posted @   五月江城  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示