P3652 shallot
問題描述
小苗去市場上買了一捆小蔥苗,她突然一時興起,於是她在每顆小蔥苗上寫上一個數字,然後把小蔥叫過來玩遊戲。
每個時刻她會給小蔥一顆小蔥苗或者是從小蔥手裏拿走一顆小蔥苗,並且讓小蔥從自己手中的小蔥苗裏選出一些小蔥苗使得選出的小蔥苗上的數字的異或和最大。
這種小問題對於小蔥來說當然不在話下,但是他的身邊沒有電腦,於是他打電話給同爲OI選手的你,你能幫幫他嗎?
你只需要輸出最大的異或和即可,若小蔥手中沒有小蔥苗則輸出嬰。
輸入格式
輸入第一行一個正整數T,表示測試數據組數。
對於每組數據,
第一行兩個正整數n表示總時間;
第二行n個整數a1; a2; …; an,若ai大於0代表給了小蔥一顆數字爲ai的小蔥
苗,否則代表從小蔥手中拿走一顆數字爲ai的小蔥苗。
輸出格式
輸出共n行,每行一個整數代表第i個時刻的最大異或和
樣例輸入
6
1 2 3 4 -2 -3
樣例輸出
1
3
3
7
7
5
提示
n<=500000,ai<=2^31-1
求從一些數中選取若干個的最大異或和,可以用線性基來做,求出這些數的一組基即可知道答案。
但是線性基只支持插入而不支持刪除,因此採用CDQ分治來維護。
預處理出每個數存在的區間 ,對時間分治,每次將存在區間與分治子區間無交的數字插入到子區間的線性基中。底層的線性基就是 時刻的答案。
實際實現時,對於每個添加,找到它對應的刪除位置,同樣對每個刪除找到對應的添加位置,然後將在左子區間添加且刪除位置在右區間右邊的數插入到右區間,將在右區間刪除且添加位置在左區間左邊的數插入到左區間,然後遞歸處理。
並不需要每個區間都維護一個線性基,而只需要每一層分治維護一個線性基,回溯的時候將上一層的copy到下一層再插入即可。
時間複雜度
事實上也可以預處理完了之後直接用線段樹維護每個位置的線性基,同樣只有插入操作,複雜度也一樣。
代碼:
#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<map>
#define N 543210
using namespace std;
struct node
{
int cnt,c[32];
void Ins(int x)
{
for(int i=1;i<=cnt;i++)
if((x^c[i])<x)x^=c[i];
if(x)c[++cnt]=x;
}
int Gans()
{
int sum=0;
for(int i=1;i<=cnt;i++)
if((sum^c[i])>sum)sum^=c[i];
return sum;
}
};
int n,a[N],ST[N],EN[N],ans[N];
node s[99];
map<int,int>fa,ct;
void CDQ(int l,int r,int x)
{
if(l==r)
{
if(a[l]>0)s[x].Ins(a[l]);
ans[l]=s[x].Gans();
return;
}
int mid=l+r>>1,i;
s[x+1]=s[x];
for(i=mid+1;i<=r;i++)
if(a[i]<0&&ST[i]<=l)s[x+1].Ins(-a[i]);
CDQ(l,mid,x+1);
s[x+1]=s[x];
for(i=l;i<=mid;i++)
if(a[i]>0&&EN[i]>r)s[x+1].Ins(a[i]);
CDQ(mid+1,r,x+1);
}
int main()
{
int i,j;
scanf("%d",&n);
for(i=1;i<=n;i++)scanf("%d",&a[i]);
for(i=1;i<=n;i++)
{
if(a[i]>0)
{
ct[a[i]]++;
if(ct[a[i]]==1)fa[a[i]]=i;
else a[i]=0;
}
else
{
ct[-a[i]]--;
if(!ct[-a[i]])
{
ST[i]=fa[-a[i]];
EN[fa[-a[i]]]=i;
}
else a[i]=0;
}
}
for(i=1;i<=n;i++)
if(a[i]>0&&!EN[i])EN[i]=n+1;
CDQ(1,n,0);
for(i=1;i<=n;i++)printf("%d\n",ans[i]);
}