2019 牛客多校 第一场 H、XOR

已知有 n 个数字 a_1,a_2,\cdot \cdot \cdot ,a_n,并且 1\leq n\leq 10^5 , 0\leq a_i\leq 10^{18},求这个区间的所有异或和为 0 的子区间的长度和。

-----------------------------------------

1、异或和为0,肯定是线性基啊。

2、直接求出所有异或和为 0 子区间,再累加长度时间上肯定承受不了,所以我们考虑求出每个数字在异或和为0的子区间中出现的次数。

3、考虑先对原数组求一次线性基,设求出的线性基的秩为 R,然后对于所有的非基底元素,他的出现的次数就是 2^{n-R -1}

4、对于每个基底元素,我们用 非基底元素和其他的基底元素 再跑一个线性基出来,设这个线性基的秩为 R1,然后判断这个数字能否成功插入到刚刚求到的线性基中,若可以,则这个数字的出现的次数是 2^{n-R1-1}

证明如下:https://www.cnblogs.com/cdcq/p/11211836.html

Code:

#include 
#define ll long long
using namespace std;
const int MAXN = 1e5 + 5;
const ll mod = 1e9 + 7;
ll a[MAXN];
ll c[100];
ll c1[100];
ll temp[100];
bool vis[MAXN];
ll power(ll a, ll b)
{
	ll res = 1;
	while (b)
	{
		if (b & 1)
			res = res * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}
bool insert(ll bas[], ll x)
{
	for (int i = 63; i >= 0; i--)
	{
		if (x & (1LL << i))
		{
			if (bas[i] == 0)
			{
				bas[i] = x;
				return true;
			}
			else
				x ^= bas[i];
		}
	}
	return false;
}
int main()
{
	int n;
	while (scanf("%d", &n) > 0)
	{
		vectorv;//线性基里面的元素
		memset(c, 0, sizeof(c));
		memset(c1, 0, sizeof(c1));
		memset(vis, 0, sizeof(vis));
		int cnt = 0, cnt1 = 0;
		for (int i = 1; i <= n; i++)
		{
			scanf("%lld", &a[i]);
			if (insert(c, a[i]))
			{
				vis[i] = true;
				cnt++;
				v.push_back(a[i]);
			}
		}
		if (v.size() == n)
		{
			printf("0\n");
			continue;
		}
		ll res = (n - cnt) * power(2, n - cnt - 1) % mod;
		for (int i = 1; i <= n; i++)
		{
			if (vis[i] == false)
			{
				if (insert(c1, a[i]))
					cnt1++;
			}
		}
		//c1是非线性基元素的线性基
		int len = v.size();
		for (int i = 0; i < len; i++)//枚举每个元素
		{
			cnt = cnt1;
			for (int i = 0; i < 100; i++)
				temp[i] = c1[i];
			for (int j = 0; j < len; j++)
			{
				if (i != j)
				{
					if (insert(temp, v[j]))
						cnt++;
				}
			}
			if (!insert(temp, v[i]))
			{
				res += power(2, n - cnt - 1);
				if (res >= mod)
					res -= mod;
			}
		}
		printf("%lld\n", res % mod);
	}
}

 

你可能感兴趣的:(2019,牛客多校赛)