BZOJ2844 albus就是要第一个出场

Address


Solution

  • 建出原序列的线性基。
  • 我们知道线性基有如下性质:
  1. 线性基中元素的子集异或和的集合和原序列的子集异或和的集合相同。
  2. 线性基中的任意元素不属于其它元素的子集异或和的集合。
  • 由性质 2 得:在线性基中任选一个子集的异或和都互不相同,若线性基中共有 k k 个元素,就共有 2 k 2^k 种互不相同的异或和。
  • 由性质 1 得:原序列可以看做是线性基中的元素再加上 n k n - k 个 0,因此每种异或和在原序列中都刚好存在 2 n k 2^{n - k} 种构造方案。
  • 于是问题被转化为:求出线性基中子集异或和小于 Q Q 的子集个数 c n t cnt ,答案为 c n t × 2 n k + 1 cnt \times 2^{n - k} + 1
  • 考虑对线性基中元素进行一些转化(等于 -1表示该位元素不存在):
	for (int i = 0; i <= 30; ++i)
		if (~A.g[i])
		for (int j = i - 1; j >= 0; --j)
			if ((~A.g[j]) && (1 << j & A.g[i]))
				A.g[i] ^= A.g[j];
  • 相当于线性基中的元素与其它元素异或,得到的仍满足线性基的性质。
  • 此时线性基中任一元素都要满足:最高位的 1 在线性基中只出现一次。
  • 因此构成 Q Q 的唯一方案为: Q Q 的所有为 1 的位在线性基中对应且存在的元素的异或和。
  • 考虑小于 Q Q 的子集至少都要不选取这些元素中的其中一些,我们枚举这些元素中不选取的位数最高的元素(假设为从低到高为第 i i 个存在的元素),那么位数更低的元素显然可以随便选,对 c n t cnt 的贡献为 2 i 2^i
  • 时间复杂度 O ( 30 n ) O(30n)

Code

  • 转化后显然对最高位的 1 不影响,实现时可以不写转化的部分。
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <cstdio>

const int Ss = 1 << 20;
char frd[Ss], *ihead = frd + Ss;
const char *itail = ihead;

inline char nxtChar()
{
    if (ihead == itail)
        fread(frd, 1, Ss, stdin), ihead = frd;
    return *ihead++;
}

template <class T>
inline void read(T &res)
{
    char ch; 
    while (ch = nxtChar(), !isdigit(ch));
    res = ch ^ 48;
    while (ch = nxtChar(), isdigit(ch))
        res = res * 10 + ch - 48;
}

typedef long long ll;
const int mod = 10086;
const int N = 1e5 + 5;
int n, m, ans, top; 
int stk[35];

struct Basis
{
	int g[35];
		
	inline void Clear()
	{
		for (int i = 30; i >= 0; --i)
			g[i] = -1;
	}
	
	inline void Insert(int x)
	{
		for (int i = 30; i >= 0; --i)	
			if (1 << i & x)
			{
				if (g[i] == -1) 
					return (void)(g[i] = x);
				x ^= g[i];
			}
	}
}A;

inline int ksm(int x, int k)
{
	int res = 1;
	while (k)
	{
		if (k & 1) res = 1ll * res * x % mod;
		x = 1ll * x * x % mod; k >>= 1;
	}
	return res;
}

int main()
{
	A.Clear();
	
	read(n);
	for (int i = 1, x; i <= n; ++i)
		read(x), A.Insert(x);

	int Q;
	read(Q);
	for (int i = 0; i <= 30; ++i)
		if (~A.g[i]) stk[top++] = i;
	for (int i = top - 1; i >= 0; --i)
		if (Q >> stk[i] & 1) ans += 1 << i;
	printf("%d\n", (1ll * ans * ksm(2, n - top) + 1) % mod);
}
发布了104 篇原创文章 · 获赞 125 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/bzjr_Log_x/article/details/85017117