知識預備
線性基的定義
由原集合得到線性基
使得中元素互相異或所形成的集合,等價於原序列的元素互相異或形成的集合
可以理解爲線性基將原序列進行了壓縮
線性基的性質
- 線性基能相互異或得到原集合的所有相互異或得到的值
- 線性基是滿足性質的最小的集合
- 線性基沒有異或和爲的子集
線性基的構造
設一個數組,表示序列的線性基,下標從開始
d[i]表示的是線性基中第個位置上所存儲的數字
如果這個數字不爲
那麼這個數字轉化爲二進制數後的第位是
且這個二進制數的最高位是第位
我們都將序列中每一個數依次插入線性基中,得到了序列的線性基
下面是向線性基中插入一個數的代碼
bool insert(ll val){//向線性基中插入一個數
for(ll i=59;i>=0;i--){
if(val&&1ll<<i){
if(d[i])val^=d[i];
else{
d[i]=val;
break;
}
}
}
return val>0;//判斷val是否插入成功
}
我們可以看到,在線性基中插入數時,從高位到低位依次掃描它爲的二進制位
當掃描到第i位時,如果不存在,就令,插入結束
如果d[i]存在,此時與的第位都爲,就令=^
於是與異或後第位變成了,然後繼續向下掃描
最終,會有插入成功和插入不成功這兩種結局
如果插入成功,就說明當前線性基裏的一些數異或起來不能等於,因此是不可替代的
如果插入不成功,是因爲當前線性基裏的一些數異或起來可以等於
並且在一系列操作之後變成了,因此是多餘的
查詢最大異或和
準確地說,是求一個序列中的若干個數的異或和的最大值
從高位到低位掃描線性基,如果答案異或後變大了,就讓答案異或
這其實是個貪心的過程,我們只需讓答案的高位儘可能大
當掃到且不爲時
由於的第位是,且d[i]的位以上都是
所以如果答案的第位是,則答案異或上之後一定會變大
如果答案的第位是,則答案異或上之後一定會變小
ll query_max(){
ll ans=0;
for(ll i=59;i>=0;i--)
if(ans^d[i]>ans)
ans^=d[i];
return ans;
}
查詢最小異或和
最小值其實就是最小的
這是因爲如果讓最小的去異或其它的,那麼它一定會變大,所以它自己就是最小的
顯然,如果這個線性基有無法插入的數,那麼最小異或和就爲
查詢第k小異或和
準確地說:從一個序列中取任意個元素進行異或,求能異或出的所有數字中第小的值。
要求第小值,首先將線性基進行改造,改造後每一個相互獨立
對於每一個,枚舉= to ,如果的第位爲1,那麼讓異或
這樣改造後,如果不爲,那麼所有的第位上爲的只有
於是線性基中的元素,作用其實都是提供自己最高位上的
那麼只要使提供出來的可以和的每一位上的對應
那麼求出來的答案就是第小的
void rebuild(){
for(int i=59;i>=0;i--)
for(int j=i-1;j>=0;j--)
if(d[i]&1ll<<j)d[i]^=d[j];
}
ll query_kth(ll k){
rebuild();
int cnt=0;
for(int i=0;i<=59;i++)if(d[i])cnt++;
if(cnt<n&&k==1)return 0;//最小異或和爲0的情況
if(cnt<n)k--;//最小異或和爲0的情況
if(k>=(1ll<<cnt))return -1;//不存在第k小異或和
ll ans=0;
for(int i=0;i<=59;i++)if(d[i]){
if(k&1)ans^=d[i];
k>>=1;
}
return ans;
}
例一:HDOJ 3949
題意
給定n(n≤10000) 個數 ,以及 Q(Q≤10000)個詢問,每次詢問這些數(至少一個,不能不選)能夠組成的異或和中第小的數是什麼(去掉重複的異或和)。
代碼
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
template<class T>inline void MAX(T &x,T y){if(y>x)x=y;}
template<class T>inline void MIN(T &x,T y){if(y<x)x=y;}
template<class T>inline void rd(T &x){
x=0;char o,f=1;
while(o=getchar(),o<48)if(o==45)f=-f;
do x=(x<<3)+(x<<1)+(o^48);
while(o=getchar(),o>47);
x*=f;
}
const int M=1e4+5;
int cs,n,m,cnt;
ll k,A[M],d[60];
void insert(ll x){
for(int i=59;i>=0;i--){
if(x&1ll<<i){
if(d[i])x^=d[i];
else{
d[i]=x;
break;
}
}
}
}
void rebuild(){
for(int i=59;i>=0;i--)
for(int j=i-1;j>=0;j--)
if(d[i]&1ll<<j)d[i]^=d[j];
}
ll query_kth(ll k){
if(cnt<n&&k==1)return 0;
if(cnt<n)k--;
if(k>=(1ll<<cnt))return -1;
ll ans=0;
for(int i=0;i<=59;i++)if(d[i]){
if(k&1)ans^=d[i];
k>>=1;
}
return ans;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("jiedai.in","r",stdin);
// freopen("jiedai.out","w",stdout);
#endif
rd(cs);
for(int cas=1;cas<=cs;cas++){
printf("Case #%d:\n",cas);
memset(d,0,sizeof(d));
rd(n);
for(int i=1;i<=n;i++)rd(A[i]),insert(A[i]);
rd(m);
rebuild();
cnt=0;
for(int i=0;i<=59;i++)if(d[i])cnt++;
while(m--)rd(k),printf("%lld\n",query_kth(k));
}
return (0-0);
}
例二:HDOJ 6579
題意
有n個數,m次操作,強制在線。
操作0 l r:詢問[l,r]的最大異或和;
操作1 x:序列的最後添加一個數x。
分析
暴力的做法可以用數據結構維護區間線性基,但肯定過不了。貪心地維護序列的前綴線性基 (上三角形態),對於每個線性基,將出現位置靠右的數 字儘可能地放在高位,也就是說在插入新數字的時候,要同時記錄對應位置上數字的出現位 置,並且在找到可以插入的位置的時候,如果新數字比位置上原來的數字更靠右,就將該位 置上原來的數字向低位推。 在求最大值的時候,從高位向低位遍歷,如果該位上的數字出現在詢問中區間左端點的 右側且可以使答案變大,就異或到答案裏。 對於線性基的每一位,與它異或過的線性基更高位置上的數字肯定都出現在它右側 (否 則它就會被插入在那個位置了),因此做法的正確性顯然。
代碼
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
template<class T>inline void MAX(T &x,T y){if(y>x)x=y;}
template<class T>inline void MIN(T &x,T y){if(y<x)x=y;}
template<class T>inline void rd(T &x){
x=0;char o,f=1;
while(o=getchar(),o<48)if(o==45)f=-f;
do x=(x<<3)+(x<<1)+(o^48);
while(o=getchar(),o>47);
x*=f;
}
const int M=1e6+5,K=31;
int n,m,cas,A[M];
int d[M][K],id[M][K];
void build(int val,int pos){
int tmp=pos;
for(int i=29;i>=0;i--){
d[pos][i]=d[pos-1][i];
id[pos][i]=id[pos-1][i];
}
for(int i=29;i>=0;i--){
if(val&1<<i){
if(d[pos][i]){
if(tmp>id[pos][i]){
swap(d[pos][i],val);
swap(id[pos][i],tmp);
}
val^=d[pos][i];
}
else{
d[pos][i]=val;
id[pos][i]=tmp;
break;
}
}
}
}
int main(){
#ifndef ONLINE_JUDGE
freopen("jiedai.in","r",stdin);
// freopen("jiedai.out","w",stdout);
#endif
rd(cas);
while(cas--){
rd(n),rd(m);
for(int i=1;i<=n;i++)rd(A[i]),build(A[i],i);
int ans=0,op,x,l,r;
while(m--){
rd(op);
if(op)rd(A[++n]),build(A[n]^=ans,n);
else{
rd(l),rd(r);
l=(l^ans)%n+1;
r=(r^ans)%n+1;
if(l>r)swap(l,r);
ans=0;
for(int i=29;i>=0;i--)if(id[r][i]>=l)MAX(ans,ans^d[r][i]);
printf("%d\n",ans);
}
}
for(int i=1;i<=n;i++)
for(int j=0;j<30;j++)
d[i][j]=id[i][j]=0;
}
return (0-0);
}