Codeforces 1336E Chiori and Doll Picking (子集和变换、线性基、阈值算法、状压 DP、组合计数)...

题目链接

https://codeforces.com/contest/1336/problem/E

题解

假设线性基大小是 \(L\),其异或值域记作 \(S\),则对于异或值域内每个数,显然有 \(2^{n-L}\) 种方案异或得到。因此只需要建一组线性基然后对这个线性基求答案即可,相当于 \(n\le m\).
数据分治。
算法一:
暴力枚举每个元素选不选。
时间复杂度 \(O(2^L)\).
算法二:
记录非自由元的状态进行状压 DP.
时间复杂度 \(O(2^{m-L}m^2)\).
结合算法一和算法二,可以通过 E1 题。
算法三:
下面约定某个数组 FWT 后的数组用对应的大写字母表示。
设长为 \(2^m\) 的数组 \(b_k[i]=[\text{bitcnt}(i)=k]\)\(c[i]=[i\in S]\)
\(k\) 的答案为 \(b_k\)\(c\) 异或卷积后为 \(0\) 的位置上的值,即 \(\frac{1}{2^m}\sum^{2^m-1}_{i=0}B_k[i]C[i]\).
根据 FWT 的定义,易得 \(B_k[i]=\sum^{2^m-1}_{j=0}(-1)^{\text{bitcnt}(i\& j)}[\text{bitcnt}(j)=k]=\sum^{\text{bitcnt}(i)}_{j=0}(-1)^j{\text{bitcnt}(i)\choose j}{m-\text{bitcnt}(i)\choose k-j}\),即 \(B_k[i]\) 只和 \(\text{bitcnt}(i)\) 有关,可以在关于 \(m\) 的多项式时间内求出。
现在考虑如何求 \(C\): \(C[i]=\sum_{x\in S} (-1)^{\text{bitcnt}(i\& x)}\),不难观察到若存在线性基里的一个元素与 \(i\)\(\text{and}\)\(\text{bitcnt}\) 为奇数,则该值一定为 \(0\)(映射法易证),否则为 \(2^L\). 那么只有那些与线性基里的每个元素 \(\text{and}\)\(\text{bitcnt}\) 都为偶数的 \(i\) 有用。而满足这个条件的 \(i\) 显然不能包含任何一个自由元,因此不超过 \(2^{m-L}\) 个,可以直接爆搜。要求的是和 \(B_k\) 点积后的结果,而 \(B_k[i]\) 的取值只和 \(\text{bitcnt(i)}\) 有关,因此只需要对每个 \(\text{bitcnt}\) 统计出 \(C_i\) 的和即可。
时间复杂度 \(O(2^{m-L}+m^3)\).
结合算法一和算法三,可以通过 E2 题。
时间复杂度 \(O(2^{\frac{m}{2}}+m^3+nm)\).

算法三最难想的是第一步吧……异或的性质好神奇,Sooke 好强!

代码

#include<bits/stdc++.h>
#define llong long long
#define mkpr make_pair
#define iter iterator
#define riter reversed_iterator
#define y1 Lorem_ipsum_dolor
using namespace std;
 
inline int read()
{
	int x = 0,f = 1; char ch = getchar();
	for(;!isdigit(ch);ch=getchar()) {if(ch=='-') f = -1;}
	for(; isdigit(ch);ch=getchar()) {x = x*10+ch-48;}
	return x*f;
}
 
const int mxN = 1<<19;
const int mxM = 53;
const int P = 998244353;
llong comb[mxM+3][mxM+3];
int bitcnt[mxN+3];
llong a[mxM+3]; int b[mxM+3];
llong ans[mxM+3];
int n,m,sz;
 
llong quickpow(llong x,llong y)
{
	llong cur = x,ret = 1ll;
	for(int i=0; y; i++)
	{
		if(y&(1ll<<i)) {y-=(1ll<<i); ret = ret*cur%P;}
		cur = cur*cur%P;
	}
	return ret;
}
 
void initfact(int n)
{
	comb[0][0] = 1ll;
	for(int i=1; i<=n; i++) {comb[i][0] = comb[i][i] = 1ll; for(int j=1; j<i; j++) {comb[i][j] = (comb[i-1][j-1]+comb[i-1][j])%P;}}
}
 
int getbitcnt(llong x)
{
	return bitcnt[x&524287]+bitcnt[(x>>19)&524287]+bitcnt[(x>>38)&524287];
}
 
namespace Solve1
{
	void dfs(int pos,llong x)
	{
		if(pos==sz) {ans[getbitcnt(x)]++; return;}
		dfs(pos+1,x); dfs(pos+1,x^a[b[pos]]);
	}
}
 
namespace Solve2
{
	llong sta[mxM+3];
	llong f[mxM+3];
	void dfs(int pos,int cnt,llong x)
	{
		if(pos==m) {f[cnt+getbitcnt(x)]++; return;}
		dfs(pos+1,cnt,x); dfs(pos+1,cnt+1,x^sta[pos]);
	}
	void solve()
	{
		for(int i=sz; i<m; i++)
		{
			for(int j=0; j<sz; j++)
			{
				if(a[b[j]]&(1ll<<b[i])) {sta[i] |= (1ll<<j);}
			}
		}
		dfs(sz,0,0ll);
//		for(int i=0; i<=m; i++) printf("%I64d ",f[i]); puts("");
		for(int k=0; k<=m; k++)
		{
			for(int i=0; i<=m; i++)
			{
				llong coe = 0ll;
				for(int j=0; j<=i&&j<=k; j++)
				{
					llong tmp = comb[i][j]*comb[m-i][k-j]%P; if(j&1) {tmp = P-tmp;}
					coe+=tmp-P,coe+=(coe>>31)&P;
				}
				coe = coe*f[i]%P;
				ans[k]+=coe-P,ans[k]+=(ans[k]>>31)&P;
			}
			ans[k] = ans[k]*quickpow((P+1ll)/2ll,m-sz)%P;
		}
	}
}
 
int main()
{
	initfact(mxM);
	for(int i=1; i<mxN; i++) bitcnt[i] = bitcnt[i>>1]+(i&1);
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
	{
		llong x; scanf("%I64d",&x);
		for(int k=m-1; k>=0; k--) if(x&(1ll<<k))
		{
			if(!a[k]) {a[k] = x; sz++; break;}
			else {x ^= a[k];}
		}
	}
	for(int i=m-1; i>=0; i--) if(a[i])
	{
		for(int j=i+1; j<m; j++) if(a[j]&(1ll<<i)) {a[j] ^= a[i];}
	}
//	for(int i=0; i<m; i++) printf("%I64d ",a[i]); puts("");
	for(int i=0,j=0; i<sz; i++)
	{
		while(!a[j]) {j++;} b[i] = j; j++;
	}
	for(int i=sz,j=0; i<m; i++)
	{
		while(a[j]) {j++;} b[i] = j; j++;
	}
	if(sz<=26)
	{
		Solve1::dfs(0,0ll);
	}
	else
	{
		Solve2::solve();
	}
	for(int i=0; i<=m; i++) ans[i] = ans[i]*quickpow(2ll,n-sz)%P;
	for(int i=0; i<=m; i++) printf("%I64d ",ans[i]); puts("");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值