【原題1】
3261: 最大異或和
Time Limit: 10 Sec Memory Limit: 512 MBSubmit: 497 Solved: 215
[Submit][Status]
Description
給定一個非負整數序列 {a},初始長度爲 N。
有 M個操作,有以下兩種操作類型:
1 、A x:添加操作,表示在序列末尾添加一個數 x,序列的長度 N+1。
2 、Q l r x:詢問操作,你需要找到一個位置 p,滿足 l<=p<=r,使得:
a[p] xor a[p+1] xor ... xor a[N] xor x 最大,輸出最大是多少。
Input
第一行包含兩個整數 N ,M,含義如問題描述所示。
第二行包含 N個非負整數,表示初始的序列 A 。
接下來 M行,每行描述一個操作,格式如題面所述。
Output
假設詢問操作有 T個,則輸出應該有 T行,每行一個整數表示詢問的答案。
Sample Input
2 6 4 3 6
A 1
Q 3 5 4
A 4
Q 5 7 0
Q 3 6 6
對於測試點 1-2,N,M<=5 。
對於測試點 3-7,N,M<=80000 。
對於測試點 8-10,N,M<=300000 。
其中測試點 1, 3, 5, 7, 9保證沒有修改操作。
對於 100% 的數據, 0<=a[i]<=10^7。
Sample Output
5
6
HINT
對於 100% 的數據, 0<=a[i]<=10^7 。
【分析】什麼事函數式trie?大概就是和函數式線段樹(主席樹)類似吧。就拿上題舉例。我們要求l--r區間中的某個數P,實際上就是要使max(Sum[N]^Sum[p-1]^x)。其中Sum表示1~i的前綴xor。(因爲xor滿足區間減性質)因爲對於每次詢問,Sum[N]^x是定值,我們不妨設爲Y。
首先,我們要把Sum[1]~Sum[N]依次插到函數式Trie中。(注意,每次只更新Log(N)個節點,剩下的點全部拷貝上一次的情況)然後我們在l到r這一段的Trie中一點一點爬。(至於+1或-1的問題自行解決。代碼中我新增了一個1號節點是0,這樣方便計算,導致後面都少了一個+1)在插入的時候我們可以記錄一個s,表示s這個點插入的時間。以爲是xor值最大,我們優先往^1處走。怎麼判斷l~r中某一位前綴二進制是否存在^1的情況呢?就用s相減即可。如果大於0,就表示中途插入過。
【代碼】(奇慢無比)
#include<cstdio>
#define N 600005
using namespace std;
int a[N*25][2],s[N*25],root[N],b[N];
int n,m,i,T,L,R,tot;
char opt[3];
void insert(int x,int &y,int Num,int d)
{
int p=(Num>>d)&1;s[y=++tot]=s[x]+1;
if (d<0) return;
a[y][p^1]=a[x][p^1];
insert(a[x][p],a[y][p],Num,d-1);
}
int Query(int x,int y,int Num,int d)
{
if (d<0) return 0;int p=(Num>>d)&1;
if (s[a[y][p^1]]-s[a[x][p^1]]) return (1<<d)+Query(a[x][p^1],a[y][p^1],Num,d-1);
return Query(a[x][p],a[y][p],Num,d-1);
}
inline int Read()
{
char ch=getchar();for (;ch<'0'||ch>'9';ch=getchar());
int x=0;for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x;
}
int main()
{
n=Read();m=Read();
insert(root[0],root[1],b[i=1]=0,24);n++;
for (i=2;i<=n;i++)
T=Read(),b[i]=b[i-1]^T,insert(root[i-1],root[i],b[i],24);
while (m--)
{
scanf("%s",opt);
if (opt[0]=='A')
T=Read(),n++,insert(root[n-1],root[n],b[n]=b[n-1]^T,24);
else
L=Read(),R=Read(),T=Read(),printf("%d\n",Query(root[L-1],root[R],b[n]^T,24));
}
return 0;
}
【原題2】
3166: [Heoi2013]Alo
Time Limit: 20 Sec Memory Limit: 256 MBSubmit: 368 Solved: 189
[Submit][Status]
Description
Welcome to ALO ( Arithmetic and Logistic Online)。這是一個VR MMORPG ,
如名字所見,到處充滿了數學的謎題。
現在你擁有n顆寶石,每顆寶石有一個能量密度,記爲ai,這些寶石的能量
密度兩兩不同。現在你可以選取連續的一些寶石(必須多於一個)進行融合,設爲 ai, ai+1, …, a j,則融合而成的寶石的能量密度爲這些寶石中能量密度的次大值
與其他任意一顆寶石的能量密度按位異或的值,即,設該段寶石能量密度次大值
爲k,則生成的寶石的能量密度爲max{k xor ap | ap ≠ k , i ≤ p ≤ j}。
現在你需要知道你怎麼選取需要融合的寶石,才能使生成的寶石能量密度最大。
Input
第一行,一個整數 n,表示寶石個數。
第二行, n個整數,分別表示a1至an,表示每顆寶石的能量密度,保證對於i ≠ j有 ai ≠ aj。
Output
輸出一行一個整數,表示最大能生成的寶石能量密度。
Sample Input
9 2 1 4 7
Sample Output
HINT
【樣例解釋】
選擇區間[1,5],最大值爲 7 xor 9。
對於 100%的數據有 1 ≤ n ≤ 50000, 0 ≤ ai ≤ 10^9
Source
【分析】剛開始我覺得無法下手:一般題目都是有Q個詢問,這裏直接就是找最大值!我們可以做如下轉化:枚舉每一個點i,將他作爲次大值,然後向左右拓展情況。假設L[i]表示第i個點左側第一個比他大的點下標,LL[I]表示第二個比他大的點的下標,R同理。那麼可以把情況分成兩種:LL[I]+1~,R[I]-1,L[I]+1~RR[I]。(顯然可以只分成一種,請自行YY)假設我們已經在可觀的效率內求出了,然後的原理和上一題差不錯,按l-1~r在Trie中爬來爬去。
問題是怎麼求?L[I]和R[I]可以用單調隊列求。LL[I]我原先是用L[L[I]]推的,但是顯然這樣是有問題的。(慚愧,在對拍的時候才發現。。。)那怎麼辦?我也想過,用類似並查集的效率求。但是如果數據是100,99,98...4,3,2,1,100,101。找101的LL值的時候會異常的慢,幾乎掃了一遍!!於是就犯難了。後來覺得LOWER_BOUND可以,但是麻煩。
諮詢了紅牛,他也用那種神方法。他很快反駁了我的數據。在計算100,99,98...4,3,2,1轉移都是O(1)級別的,所以均攤效率是O(N)。兇!寫了他的方法後,就機智地A掉了,而且跑的飛起。。。
【附造數據程序】
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<map>
using namespace std;
map<int,int>Map;
int main()
{
freopen("3166.in","w",stdout);
srand((int)time(0));
int n=200,p;
printf("%d\n",n);
Map[0]=1;
for (int i=1;i<=n;i++)
{
for (p=0;Map[p];p=rand()%10000);
printf("%d\n",p);Map[p]=1;
}
return 0;
}
【代碼】(暫時RANK 1)
#include<cstdio>
#include<algorithm>
#define N 50005
using namespace std;
int A[N],B[N],L[N],R[N],LL[N],RR[N],q[N],root[N],s[N*200],a[N*200][2];
int n,h,t,i,ans,tot,Max,temp;
void insert(int x,int &y,int Num,int d)
{
s[y=++tot]=s[x]+1;
if (d<0) return;
int p=(Num>>d)&1,temp=y;
a[y][p^1]=a[x][p^1];
insert(a[x][p],a[y][p],Num,d-1);
}
int Query(int x,int y,int Num,int d)
{
if (d<0) return 0;int p=(Num>>d)&1;
if (s[a[y][p^1]]-s[a[x][p^1]]) return (1<<d)+Query(a[x][p^1],a[y][p^1],Num,d-1);
return Query(a[x][p],a[y][p],Num,d-1);
}
inline int Read()
{
char ch=getchar();for (;ch<'0'||ch>'9';ch=getchar());
int x=0;for (;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x;
}
inline int wentL(int k)
{
if (A[k]>A[i]||!k) return k;
return wentL(L[k]);
}
inline int wentR(int k)
{
if (A[k]>A[i]||k==n+1) return k;
return wentR(R[k]);
}
int main()
{
n=Read();
for (i=1;i<=n;i++) A[i]=Read(),Max=max(Max,A[i]);
h=t=0;
for (i=1;i<=n;i++)
{
while (h<t&&A[i]>=A[q[t]]) t--;
L[i]=(h>=t)?0:q[t];
//LL[i]=(h+1>=t)?0:q[t-1];
q[++t]=i;
}
h=t=0;
for (i=n;i;i--)
{
while (h<t&&A[i]>=A[q[t]]) t--;
R[i]=(h>=t)?n+1:q[t];
//RR[i]=(h+1>=t)?0:q[t-1];
q[++t]=i;
}
L[0]=0;R[n+1]=n+1;
for (i=2;i<=n;i++) LL[i]=L[i]?wentL(L[i]-1):0;
RR[n]=n+1;for (i=n-1;i;i--)
RR[i]=R[i]<=n?wentR(R[i]+1):n+1;
insert(root[n+1],root[0],0,30);
for (i=1;i<=n;i++)
insert(root[i-1],root[i],A[i],30);
for (i=1;i<=n;i++)
{
if (A[i]==Max) continue;
if (L[i]) ans=max(ans,temp=Query(root[LL[i]],root[R[i]-1],A[i],30));
if (R[i]<=n) ans=max(ans,temp=Query(root[L[i]],root[RR[i]-1],A[i],30));
}
printf("%d",ans);
return 0;
}