2019HDU多校赛 第一场 D HDU 6579 Operation(线性基 + 贪心)_HDU

 

 

大致题意:给你一个长度为n的序列。有两个操作,0表示询问在一个区间中取任意数字使得异或和最大,1表示在这个序列的最后加上一个数字x。强制在线。

最显然的暴力方法就是对整个区间用数据结构维护区间的线性基,对于每个区间进行线性基的合并然后求出最大值。但是本题数据较大,不管是从时间上还是空间上都过不去。然后考虑分块的做法,分块虽然空间复杂度不成问题,但是在查询的时候,复杂度可以到

2019HDU多校赛 第一场 D HDU 6579 Operation(线性基 + 贪心)_贪心_02

,然后还有1e5级别的询问,时间上是过不去的。我们考虑分析一下线性基的性质,可以发现对于同样一组数字可以有不同的线性基。考虑维护所有1到i的前缀线性基,对于所有右端点为i的询问,显然越靠近右边的数字可以被越多的数字用到。又要求异或和尽可能的大,所以我们要尽量把用得多的数字放到高位。也就是说,对于后加入的数字,如果他能够放在某一个高位,那么替换原有的基,让更多的区间在高位能够有基可用。这个操作,我们可以在

2019HDU多校赛 第一场 D HDU 6579 Operation(线性基 + 贪心)_#define_03

的时间内完成。

然后对于每一个询问,我们只需要在线性基里面找那些,异或之后可以变大,而且出现位置在左端点右边的基,把他们异或起来就是最后的答案。

这种方法的思想本质上就是一种贪心。当然了,这题用ST表套分块貌似也是可以过的,但是会复杂很多,用ST表去维护块之间的线性基,每个块的大小为logN。这里就不说了。

具体见代码:

#include<bits/stdc++.h>
#define N 1000010
#define INF 0x3f3f3f3f
#define eps 1e-10
#define pi 3.141592653589793
#define P 1000000007
#define LL long long
#define pb push_back
#define fi first
#define se second
#define cl clear
#define si size
#define lb lower_bound
#define ub upper_bound
#define mem(x) memset(x,0,sizeof x)
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%d%d",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x,&y,&z)
using namespace std;

int f[N][30],pos[N][30];
int n,m,ans=0,a[N];

inline void ins(int i,int x)
{
int k=i;
memcpy(f[i],f[i-1],sizeof(f[i]));
memcpy(pos[i],pos[i-1],sizeof(pos[i]));
for(int j=29;j>=0;j--)
if (x>>j)
{
if (!f[i][j])
{
f[i][j]=x;
pos[i][j]=k;
break;
} else
{
if (k>pos[i][j]) swap(x,f[i][j]),swap(k,pos[i][j]);
x^=f[i][j];
}
}
}

int main()
{
int T_T; sc(T_T);
while(T_T--)
{
scc(n,m); ans=0;
for(int i=1;i<=n;i++)
{
int x; sc(x);
ins(i,x);
}
while(m--)
{
int op; sc(op);
if (op)
{
int x; sc(x);
x^=ans; ins(++n,x);
} else
{
int l,r,res=0; scc(l,r);
l=(l^ans)%n+1; r=(r^ans)%n+1;
if (l>r) swap(l,r);
for(int i=29;i>=0;i--)
if (pos[r][i]>=l) res=max(res,res^f[r][i]);
printf("%d\n",ans=res);
}
}
for(int i=1;i<=n;i++)
{
memset(f[i],0,sizeof(f[i]));
memset(pos[i],0,sizeof(pos[i]));
}
}
}