函數式trie思想 & Bzoj 3261 & 3166 題解

【原題1】

3261: 最大異或和

Time Limit: 10 Sec  Memory Limit: 512 MB
Submit: 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

5 5
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

4
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 MB
Submit: 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

5
9 2 1 4 7


Sample Output

14

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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章