ARC156D 题解

相当妙的题。

异或有个很好的性质,就是异或两次会抵消。这样我们只需要考虑出现次数为奇数的方案即可。

我们设 \(t_i\) 表示 \(a_i\) 的出现次数。那么这种方案的和 \(sum=\sum_{i=1}^{n}a_it_i\),出现次数为

\[\prod_{i=1}^{n}\binom{k-\sum_{j=1}^{i-1}t_j}{t_i} \]

可以这么理解:先把前 \(i-1\) 个数选了,然后考虑 \(a_i\) 放在哪些位置。

由于我们要求其 \(\bmod 2\) 的值是否为 \(1\),而只有一堆 \(1\) 乘起来才是 \(1\),所以只有当每个组合数都是 \(1\) 的时候结果才是 \(1\)。现在问题转化为:给你一堆组合数,判断是否每个组合数对 \(2\) 取模的值都是 \(1\)

由 Lucas 定理,可得 \(\binom{n}{m}\bmod 2=1\) 当且仅当不存在任何一个 \(i\),使得 \(m\) 的第 \(i\) 位为 \(1\)\(n\) 的第 \(i\) 位为 \(0\)。换句话说,\(m\) 的二进制表示是 \(n\) 的二进制表示的子集

再代回原式,\(t_1\)\(k\) 的子集,\(t_2\)\(k-t_1\) 的子集……我们会发现 \(t_1,...t_n\) 构成了 \(k\) 的一个划分!换句话说,对于 \(k\) 二进制下的每一位 \(1\),其只能分配给某一个 \(t_i\)

然后我们逐位计算异或和。考虑数位 dp,设 \(f_{i,j}\) 表示从低到高考虑到第 \(i\) 位,\(j\)\(sum\) 截掉 \(i-1\) 及更低位之后的值(也就是 \(sum\) 除以 \(2^{i-1}\) 之后的值)。因为前 \(i-1\) 位都计算过了,就只需要考虑后面的位。

初始有 \(f_{0,0}=1\)。然后考虑转移。假设枚举到第 \(i\) 位(\(0\sim60\)不是 \(k\) 的每一位),\(k\) 在第 \(i\) 位上的值为 \(up\),那么有:

  • \(f_{i-1,j}=0\)。方案数如果已经是偶数了那之后再怎么乘还是偶数,跳过。

  • \(up=0\)\(f_{i,\lfloor\frac{j}{2}\rfloor}\leftarrow f_{i,\lfloor\frac{j}{2}\rfloor}\oplus f_{i,j}\) ,即 \(f_{i,\lfloor\frac{j}{2}\rfloor}\leftarrow f_{i,\lfloor\frac{j}{2}\rfloor}\oplus 1\)

  • \(up=1\),枚举这一位选什么。设选了 \(a_p\),那么 \(f_{i,\lfloor\frac{j+a_p}{2}\rfloor}\leftarrow f_{i,\lfloor\frac{j+a_p}{2}\rfloor}\oplus f_{i,j}\) ,即 \(f_{i,\lfloor\frac{j+a_p}{2}\rfloor}\leftarrow f_{i,\lfloor\frac{j+a_p}{2}\rfloor}\oplus 1\)

最后在 dp 过程中统计答案。

\(i\) 位的贡献不仅要考虑 \(f_{i,j}\),还要考虑后面没选的位的方案数。设 \(k\) 一共有 \(M\)\(1\),当前考虑了前 \(m\)\(1\),则后面的位的方案数为 \(n^{M-m}\)。显然这玩意只有当 \(n\) 为奇数或者 \(M=m\) 的时候才是奇数,会对答案产生贡献。

第一个条件容易判断,第二个条件可以记录一下 \(k\) 最高的 \(1\) 是哪一位。假设为 \(high\),那么只有当此时的 \(i\ge high\) 时满足条件。最后还要判断 \(sum\) 的第 \(i\) 位是否为 \(1\),直接查 \(j/j+a_p\) 的最低位即可。如果都满足条件,就给答案异或上 \(2^i\)

时间复杂度 \(O(n\max a_i\log k)\),因为 \(j\) 的上界只有 \(\max a_i\)。空间容易通过滚动数组优化到 \(O(n+\max a_i)\),但是注意 \(j\) 还要加上个 \(a_p\) 所以 dp 数组要开到 \(2\max a_i\)

代码:

#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define gt getchar
#define pt putchar
#define fst first
#define scd second
typedef long long ll;
const int N=2005;
using namespace std;
using namespace __gnu_pbds;
typedef pair<int,int> pii;
inline bool __(char ch){return ch>=48&&ch<=57;}
template<class T> inline void read(T &x){
	x=0;bool sgn=0;char ch=gt();
	while(!__(ch)&&ch!=EOF) sgn|=(ch=='-'),ch=gt();
	while(__(ch)) x=(x<<1)+(x<<3)+(ch&15),ch=gt();
	if(sgn) x=-x;
}
template<class T,class ...T1> inline void read(T &x,T1 &...x1){
	read(x);
	read(x1...);
}
template<class T> inline void print(T x){
	static char st[70];short top=0;
	if(x<0) pt('-');
 	do{st[++top]=x>=0?(x%10+48):(-(x%10)+48),x/=10;}while(x);
    while(top) pt(st[top--]);
}
template<class T> inline void printsp(T x){
	print(x);
	putchar(' ');
}
template<class T> inline void println(T x){
	print(x);
	putchar('\n');
}
int n,a[N],high;
ll k,ans;
bool f[N],g[N];
signed main(){
	read(n,k);
	for(int i=1;i<=n;++i) read(a[i]);
	high=60;
	while(!((k>>high)&1)) high--;
	f[0]=1;
	for(int x=0;x<=60;++x){
		memset(g,0,sizeof(g));
		int up=(k>>x)&1;
		for(int val1=0;val1<=1000;++val1){
			if(!f[val1]) continue;
			if(!up){
				if(((n%2)||(x>=high))&&(val1&1)) ans^=(1ll<<x);
				g[val1>>1]^=1;
			}else{
				for(int i=1;i<=n;++i){
					int val2=val1+a[i];
					if(((n%2)||(x>=high))&&(val2&1)) ans^=(1ll<<x);
					g[val2>>1]^=1;
				}
			}
		}
		memcpy(f,g,sizeof(f));
	}
	println(ans);
	return 0;
}
posted @ 2024-02-28 15:13  Southern_Dynasty  阅读(5)  评论(0编辑  收藏  举报