CF959F题解(线性基)

题面

Mahmoud and Ehab and yet another xor task
题意:

         ⋅ \cdot 给定一个 n n n 个数的序列 { a i } \{ a_i \} {ai}
         ⋅ \cdot q q q 次形如 ( l , x ) (l,x) (l,x) 的询问,每次询问要求输出前 l l l 个数中,有多少子序列满足异或和等于 x x x
         ⋅ \cdot 1 ≤ n , q ≤ 1 0 5 , 0 ≤ a i ≤ 2 20 1 \leq n,q \leq 10^5,0 \leq a_i \leq 2^{20} 1nq1050ai220

分析:

         解决这类 从一堆数中选出若干个,使得它们的异或和为某一个数,问有多少种选法 的题,有一种固定的思路:

        构建这堆数的 线性基,然后把这堆数分成两类:在线性基中的数 和 不在线性基中的数。 设第二类数的数量为 n n n,如果线性基能够异或出来数 x x x,那么答案就是 2 n 2^n 2n。否则答案为 0 0 0

         我们考虑证明一下: 首先我们需要明确,对于一个线性基而言,如果它能异或出来一个数,那么选取方案 有且只有一个。 我们考虑对于这堆数的第二类数,它们所有能异或出来的数字,线性基都可以唯一的异或出来。 如果线性基能够唯一异或出来 x x x,那么我们在非线性基的数中任选,设这些数的异或值为 y y y,那么线性基只要能异或出来 x ⊕ y x \oplus y xy,那么就可以构出一种方案。因为 线性基能选一些数异或出来 x x x,又能选一些数异或出来 y y y,我们将这两拨数异或就得到了 x ⊕ y x \oplus y xy。所以第二堆数都可以 选或不选,所有的方案就是 2 n 2^n 2n

         为什么 当线性基异或不出来 x x x 时,答案为 0 0 0 呢?因为 线性基 和 原序列能异或得到的数字集合一模一样 的,如果线性基都异或不出来,那么原序列也肯定异或不出来。

         对于本题而言。因为是多次询问,我们将询问离线,使 l l l 从前往后递增,这样每次把新数插入线性基就好了。复杂度 O ( n ∗ 20 ) O(n * 20) O(n20)

CODE:

#include<bits/stdc++.h>// 答案 2^|S| 
#define mod 1000000007
using namespace std;// 某一集合异或出某一个数字的方案数的通法
typedef long long LL; 
const int N = 1e5 + 10;
int n, a[N], t;
LL ans[N];
namespace B{
	int b[30], cnt = 0;
	void ins(int x){
		for(int i = 20; i >= 0; i--)
			if((x >> i) & 1)
				if(!b[i]){b[i] = x; cnt++; break;}
				else x ^= b[i];
	}
	bool query(int x){// x能否得到 
		for(int i = 20; i >= 0; i--){
			if((x >> i) & 1){
				if(b[i]) x ^= b[i];
				else return 0;
			}
		}
		return 1;
	}
}
struct Question{
	int l, x, id;
}q[N];
bool cmp(Question a, Question b){return a.l < b.l;}
LL Pow(LL x, LL y){
	LL res = 1, k = x;
	while(y){
		if(y & 1) res = (res * k) % mod;
		y >>= 1;
		k = (k * k) % mod;
	}
	return res;
}
LL ask(int m){//第 m 个问题 
	for(int i = q[m - 1].l + 1; i <= q[m].l; i++)
		B::ins(a[i]);
	if(B::query(q[m].x)) return Pow(2LL, q[m].l - B::cnt);
	else return 0;
}
int main(){
	scanf("%d%d", &n, &t);
	for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for(int i = 1; i <= t; i++){
		scanf("%d%d", &q[i].l, &q[i].x);
		q[i].id = i;
	}
	sort(q + 1, q + t + 1, cmp);
	for(int i = 1; i <= t; i++){
		ans[q[i].id] = ask(i);
	}
	for(int i = 1; i <= t; i++) printf("%lld\n", ans[i]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值