0/1字典树解决dp问题
D2 - Xor-Subsequence (hard version)
题意
给定数组,找到最长的子序列,其中的所有数字满足\(a_{bp}⊕b_{p+1}<a_{bp+1}⊕b_p\)。其中\(p\)为\(a\)的下标。
solution
分析\(a_{bp}⊕b_{p+1}<a_{bp+1}⊕b_p\),需要在二进制的前\(k\)位两边相同,在\(k+1\)位满足\(a_{bp}⊕b_{p+1}=0\),\(a_{bp+1}⊕b_p=1\),
方便说明,我们使用\(j=b_p,i=b_{p+1}\)。
前\(k\)位满足\(a_j⊕i=a_i⊕j\),因此\(a_j⊕j=a_i⊕i\),我们想到使用一棵01字典树去记录\(a_j⊕j\)和该节点子孙的最大价值。
在第\(k+1\)位,由于\(a_{i}⊕j=1,a_{j}⊕i=0\),可以得到\(a_{i}⊕i!=a_{j}⊕j\),但是在字典树上,我们不能由\(a_{i}⊕i!=a_{j}⊕j\)直接得到答案,因为这是一个充分不必要的条件,无法反过来推,因此我们也需要记录一下\(i\)的状态。
这会让问题复杂吗?不会,\(i\)也只有\(0/1\)状态,写个\(val[tree][0/1]\)记录在\(tree\)节点的\(0/1\)最大值即可。
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5+7;
int a[N],nex[N*30][2],val[N*30][2], ans[N];
int cnt=1;
int find(int x, int t) {
int res = 1, cur = 1;
for (int i = 29; i >= 0 && cur; i--) {
int v = nex[cur][!(x >> i & 1)];
res = max(res, val[v][!(t >> i & 1)] + 1);
cur = nex[cur][x >> i & 1];
}
return res;
}
void insert(int x, int k) {
int cur = 1;
for(int i = 29 ; i >= 0 ; i--) {
if(nex[cur][x>>i & 1]==0)
nex[cur][x>>i & 1] = ++cnt;
cur = nex[cur][x>>i & 1];
val[cur][k>>i & 1] = max(val[cur][k>>i & 1], ans[k]);
}
}
void solve() {
int n;
cin >> n;
for(int i = 0 ; i < n ; i++ )
scanf("%d",&a[i]);
int res = 0;
for(int i = 0 ; i < n ; i++ ) {
ans[i] = find(a[i]^i,a[i]);
insert(a[i]^i,i);
res = max(ans[i], res);
}
cout << res << endl;
for(int i = 0 ; i <= cnt ; i++) {
for(int j = 0 ; j < 2 ; j++ ) {
nex[i][j] = 0;
val[i][j] = 0;
}
}
cnt = 1;
}
int main() {
int tc;
cin >> tc;
while(tc--)
solve();
}

浙公网安备 33010602011771号