原題連結:https://www.luogu.com.cn/problem/P4735
題意解讀:已知長度為n的陣列a[],要在l~r範圍找到一個p,使得a[p]^a[p+1]^...^a[n]^x最大,求這個最大的異或值。
解題思路:
1、利用字首和將問題轉化
設s[]是a[]的字首異或陣列,要計算a中一段範圍l~r的異或,可以藉助於s
由於s[r] = a[0]^a[1]^...a[l-1]^a[l]^...^a[r],s[l-1] = a[0]^a[1]^...^a[l-1]
因此s[r] ^ s[l-1] = a[l]^a[l+1]^...^a[r]
對於a[p]^a[p+1]^...^a[n]^x,可以轉化為s[n]^x^s[p-1],即問題轉化為在s[l-1]~s[r-1]範圍內找到一個s[p-1]使得s[n]^x與其異或值最大。
要計算與一個數異或最大的值,可以藉助於Trie樹,但是這裡限制條件是要在一個範圍內找到與某個數異或的最大值,需要引入一種新的資料結構-持久化Trie樹。
2、持久化Trie樹介紹
所謂持久化Trie,一般用於01Trie,是指對每一個數字進行加入trie操作時,都建立一個新的根節點,從該根節點可以查詢到之前新增的所有的數,具體如何實現呢?我們以2 6 4 3 為例進行Trie新增操作:
新增的過程有以下特點:
- 每新增一個數s[i],都建立一個根節點root[i],從root[i]可以查詢到s[1]~s[i]所有數
- 當新增s[i]時,對於s[i]的每一個二進位制位都新建立節點,對於不在s[i]中的二進位制位,複製前一個根的節點
- 如新增6時,trie[6][0]=trie[1][0]是複製前一個根節點,而trie[6][1]=7是新建節點
查詢的過程:
要在L~R範圍內查詢與x異或最大的結果,可以在根為root[R]中去找1~R範圍的數,同時用一個maxid[]來記錄Trie樹中每一個節點所經過的數的最大id,這樣在查詢最大異或值時,只需要判斷相反位節點存在且maxid[]>=L即說明是L~R範圍的數,不存在則走相同位的節點。
Trie樹操作核心程式碼:
//將s[id]新增到持久化Trie
void add(int id)
{
root[id] = ++idx; //建立當前根節點
int cur = root[id]; //當前根
int pre = root[id - 1]; //前一個根
for(int i = 23; i >= 0; i--)
{
int v = s[id] >> i & 1;
trie[cur][!v] = trie[pre][!v]; //相反位複製上一個根
trie[cur][v] = ++idx; //對s[i]每個二進位制位新建節點
maxid[cur] = id; //更新maxid
cur = trie[cur][v], pre = trie[pre][v]; //根沿著s[i]的二進位制位往下走
}
maxid[cur] = id; //更新maxid
}
//在l~r範圍內查詢與val異或的最大值
int find(int l, int r, int val)
{
int res = 0;
int u = root[r]; //根
for(int i = 23; i >= 0; i--)
{
int v = val >> i & 1;
if(maxid[trie[u][!v]] >= l) //如果相反位的節點存在且最大編號>=l
{
u = trie[u][!v];
res = res * 2 + 1; //異或後1對結果的貢獻
}
else
{
u = trie[u][v];
res = res * 2; //異或後0對結果的貢獻
}
}
return res;
}
3、最終問題求解
第一步:計算字首異或
第二步:建立持久化Trie
第三步:範圍查詢最大異或值
100分程式碼:
#include <bits/stdc++.h>
using namespace std;
const int N = 600005, M = N * 24;
int s[N]; //字首異或陣列
int root[N]; //所有版本trie的根節點
int trie[M][2], idx, maxid[M]; //trie樹,maxid[]儲存經過某個節點的數的最大s編號
int n, m;
char op;
int l, r, x;
//將s[id]新增到持久化Trie
void add(int id)
{
root[id] = ++idx; //建立當前根節點
int cur = root[id]; //當前根
int pre = root[id - 1]; //前一個根
for(int i = 23; i >= 0; i--)
{
int v = s[id] >> i & 1;
trie[cur][!v] = trie[pre][!v]; //相反位複製上一個根
trie[cur][v] = ++idx; //對s[i]每個二進位制位新建節點
maxid[cur] = id; //更新maxid
cur = trie[cur][v], pre = trie[pre][v]; //根沿著s[i]的二進位制位往下走
}
maxid[cur] = id; //更新maxid
}
//在l~r範圍內查詢與val異或的最大值
int find(int l, int r, int val)
{
int res = 0;
int u = root[r]; //根
for(int i = 23; i >= 0; i--)
{
int v = val >> i & 1;
if(maxid[trie[u][!v]] >= l) //如果相反位的節點存在且最大編號>=l
{
u = trie[u][!v];
res = res * 2 + 1; //異或後1對結果的貢獻
}
else
{
u = trie[u][v];
res = res * 2; //異或後0對結果的貢獻
}
}
return res;
}
int main()
{
cin >> n >> m;
maxid[0] = -1; //不存在的節點maxid=-1,便於find中比較
add(0); //第一個數新增0,使得便於區間計算
for(int i = 1; i <= n; i++)
{
cin >> s[i];
s[i] = s[i - 1] ^ s[i]; //計算字首異或
add(i); //新增到持久化Trie
}
while(m--)
{
cin >> op;
if(op == 'A')
{
cin >> x;
s[n + 1] = s[n] ^ x;
n++;
add(n);
}
else
{
cin >> l >> r >> x;
cout << find(l - 1, r - 1, x ^ s[n]) << endl;
}
}
}