2019 Multi-University Training Contest 1:Operation(贪心 + 线性基)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6579
在这里插入图片描述
题目大意:给一个初始长度为n的序列,有两种操作:
0 l r:询问[l,r]最大子集异或和
1 x:将x加在序列尾部,使得序列边成一个长度为n + 1的新序列

(看到子集异或和就想到了线性基,但数据范围太大,如果用线段树暴力维护区间线性基,复杂度会达到 O ( n ∗ l o g 3 n ) O(n * log^3n) O(nlog3n),甚至空间也会超出限制,寻思着应该有其它的方法,网上搜到了用 0 1字典树维护并求在[l,r]区间内选一个数出来和某个数异或最大(待补),万能的群友说这题是CF1100F原题,一看果然是,范围是一样大的)

题解:不可能用线段树这类数据结构去维护区间线性基,考虑贪心维护前缀线性基,对于前缀线性基,从高行到低行每一个向量的下标应尽量靠右,在构造线性基时,对于新加入的元素,从高位到低位遍历向量,换掉位置比他靠前的向量,对于被换出来的向量,在与现在的行向量异或后,对剩下还没枚举到的行做相同处理,不断处理下去直到从高行到低行每一行的向量都是尽量靠右的。

求异或最大值时,从高行到低行判断前缀前行基中每一个行向量的下标是否 在 l 的右侧,如果在l的右侧:答案取当前值和异或上该向量后的值中的最大值,如果不在就continue了。
(这种做法和洛谷树状数组专题的HH的项链有点类似,虽然它们都是这一行的向量,但由于新的向量更靠右,对于这个右区间更大的区间来说这个新的行向量贡献更大)

正确性证明:正确性很显然,对于某个区间[l,r]内的元素,都被尽可能大地作为[1,r]前缀线性基的向量。

#include<bits/stdc++.h>
using namespace std;
int t,n,m;
const int maxn = 1e6 + 10;
int f[maxn][30],p[maxn][30],a[maxn];
int main() {
	scanf("%d",&t);
	while(t--) {
		scanf("%d%d",&n,&m);
		memset(f,0,sizeof f);
		memset(p,0,sizeof p);
		for(int i = 1; i <= n; i++)
			scanf("%d",&a[i]);
		for(int i = 1; i <= n; i++) {
			for(int j = 29; j >= 0; j--) {
				f[i][j] = f[i - 1][j];
				p[i][j] = p[i - 1][j]; 
			}
			int tmp = a[i],cp = i;
			for(int j = 29; j >= 0; j--) {
				if(tmp >> j) {
					if(f[i][j]) {
						if(p[i][j] < cp) {
							swap(p[i][j],cp);
							swap(f[i][j],tmp);
						}
						tmp ^= f[i][j];
					}
					else {
						f[i][j] = tmp;
						p[i][j] = cp;
						break;
					}
				}
			}
		}
		int ans = 0;
		for(int i = 1; i <= m; i++) {
			int type,l,r;
			scanf("%d",&type);
			if(type == 0) {
				scanf("%d%d",&l,&r);
				l ^= ans; l = l % n + 1;
				r ^= ans; r = r % n + 1;
				if(l > r) swap(l,r);
				int res = 0;
				for(int j = 29; j >= 0; j--)
					if(f[r][j] && p[r][j] >= l)
						res = max(res,res ^ f[r][j]);
				printf("%d\n",ans = res);
			}
			else {
				scanf("%d",&l);
				l ^= ans;
				a[++n] = l;
				int tmp = a[n];
				int cp = n;
				for(int j = 29; j >= 0; j--)
					f[n][j] = f[n - 1][j],p[n][j] = p[n - 1][j];
				for(int j = 29; j >= 0; j--) {
					if(tmp >> j){
						if(f[n][j]) {
							if(p[n][j] < cp) {
								swap(cp,p[n][j]);
								swap(f[n][j],tmp);
							}
							tmp ^= f[n][j];
						}
						else {
							f[n][j] = tmp;
							p[n][j] = cp;
							break;
						}
					}
				}					
			}
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值