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();
}
posted @ 2022-09-02 11:27  seekerHeron  阅读(48)  评论(0)    收藏  举报