【bzoj4571】[Scoi2016]美味 主席树
题目描述
给出一个长度为n的序列,m询问,每次询问求出[l,r]范围内的每一个数加上x再与b异或能够得到的最大值。
输入
第1行,两个整数,n,m,表示菜品数和顾客数。
第2行,n个整数,a1,a2,...,an,表示每道菜的评价值。
第3至m+2行,每行4个整数,b,x,l,r,表示该位顾客的期望值,偏好值,和可以选择菜品区间。
1≤n≤2×10^5,0≤ai,bi,xi<10^5,1≤li≤ri≤n(1≤i≤m);1≤m≤10^5
输出
输出 m 行,每行 1 个整数,ymax ,表示该位顾客选择的最美味的菜的美味值。
样例输入
4 4
1 2 3 4
1 4 1 4
2 3 2 3
3 2 3 3
4 1 2 4
样例输出
9
7
6
7
题解
主席树
如果没有+x这个条件显然是可持久化Trie树。
有了这个条件以后不能直接使用可持久化Trie树,但是按位贪心的这个方法仍然可以使用。
考虑每一位,把满足高位的情况下,使得该位为1的a+x的范围拿出来,减去x得到a的范围。如果[l,r]中这个范围内有数,则该位为1;否则该位为0。
这样从高位到地位枚举,每次会将当前区间分成两部分(当前位为0和当前位为1),不断确定区间中数的个数即可出解。
时间复杂度$O(n\log^2 n)$。
(做完本题后可以回想一下01Trie树的实质:权值范围为$[0,2^k)$的权值线段树。权值范围使得每次查询都可以直接判断子树大小以及O(1)移动节点指针,使得查询复杂度变为$O(\log n)$)
#include <cstdio> #include <cstring> #include <algorithm> #define N 200010 #define M 1000000 #define lson l , mid , ls[x] , ls[y] #define rson mid + 1 , r , rs[x] , rs[y] using namespace std; int ls[N * 30] , rs[N * 30] , si[N * 30] , tot , root[N]; void insert(int p , int l , int r , int x , int &y) { y = ++tot , si[y] = si[x] + 1; if(l == r) return; int mid = (l + r) >> 1; if(p <= mid) rs[y] = rs[x] , insert(p , lson); else ls[y] = ls[x] , insert(p , rson); } int query(int b , int e , int l , int r , int x , int y) { if(b <= l && r <= e) return si[y] - si[x]; int mid = (l + r) >> 1 , ans = 0; if(b <= mid) ans += query(b , e , lson); if(e > mid) ans += query(b , e , rson); return ans; } int solve(int b , int x , int l , int r) { int i , now = 0 , y , z , ans = 0; for(i = 1 << 17 ; i ; i >>= 1) { if(b & i) y = now , z = now + i - 1; else y = now + i , z = now + 2 * i - 1; y -= x , z -= x; if(query(y , z , -M , M , root[l - 1] , root[r])) { ans |= i; if(!(b & i)) now |= i; } else if(b & i) now |= i; } return ans; } int main() { int n , m , i , b , x , l , r; scanf("%d%d" , &n , &m); for(i = 1 ; i <= n ; i ++ ) scanf("%d" , &x) , insert(x , -M , M , root[i - 1] , root[i]); for(i = 1 ; i <= m ; i ++ ) scanf("%d%d%d%d" , &b , &x , &l , &r) , printf("%d\n" , solve(b , x , l , r)); return 0; }