函数式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;
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值