1. 程式人生 > 實用技巧 >#trie#A 區間異或

#trie#A 區間異或

題目

給定一個長度為\(n\)的序列,詢問有多少個\((l,r),1\leq l\leq r\leq n\)滿足

\[xor_{l\leq j\leq r}a_j\geq k \]


分析

顯然跑一次字首和就變成查詢兩個數的異或值是否不少於\(k\)
那麼這顯然可以轉換成01trie的問題
按照二進位制位處理,分類討論:

  1. 當前\(x\)\(k\)的二進位制位為1,那麼跳到\(trie[p][0]\)
  2. 當前\(x\)\(k\)的二進位制位為0,那麼只要讓該二進位制位為1就能使異或值大於\(k\),然後累計答案再跳到\(trie[p][0]\)
  3. 當前\(x\)的二進位制位為0,\(k\)的二進位制位為1,那麼跳到\(trie[p][1]\)
  4. 當前\(x\)的二進位制位為1,\(k\)的二進位制位為0,那麼只要讓該二進位制位為0就能使異或值大於\(k\),然後累計答案再跳到\(trie[p][1]\)

程式碼

#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
const int N=1000011;
int a[N],n,m; long long ans;
inline signed iut(){
	rr int ans=0; rr char c=getchar();
	while (!isdigit(c)) c=getchar();
	while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
	return ans;
}
struct Trie{
	int trie[N*30][2],cnt[N*30],tot;
	inline void Clear(){
		trie[1][0]=trie[1][1]=cnt[1]=0,tot=1;
	}
	inline void Insert(int x){
		rr int p=1;
		for (rr int i=29;~i;--i){
			rr int z=(x>>i)&1;
			if (!trie[p][z]){
				trie[p][z]=++tot,cnt[tot]=0,
				trie[tot][0]=trie[tot][1]=0;
			}
			++cnt[p],p=trie[p][z];
		}
		++cnt[p];
	}
	inline signed query(int x){
		rr int p=1,ans=0;
		for (rr int i=29;~i;--i){
			if (!p) return ans;
			if ((x>>i)&1){
				if ((m>>i)&1) p=trie[p][0];
				    else ans+=cnt[trie[p][0]],p=trie[p][1];
			}else{
				if ((m>>i)&1) p=trie[p][1];
				    else ans+=cnt[trie[p][1]],p=trie[p][0];
			}
		}
		return ans+cnt[p];
	}
}trie;
signed main(){
	freopen("xor.in","r",stdin);
	freopen("xor.out","w",stdout); 
	for (rr int T=iut();T;--T){
		trie.Clear(),trie.Insert(0),
		n=iut(),m=iut(),ans=0;
		for (rr int i=1,x=0;i<=n;++i){
			x^=iut(),
			ans+=trie.query(x),
			trie.Insert(x);
		}
		printf("%lld\n",ans);
	}
	return 0;
}