洛谷题单指南-线段树的进阶用法-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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?