P6442 [COCI2011-2012#6] KOŠARE

written on 2022-07-08

一开始看到 \(m\) 的数据范围,很明显一眼状压。但是 \(n\) 的范围较大,因此暴力的一个一个箱子进行状压转移只能拿到 \(50pts\)。对这种需要顺序枚举 \(n\) 的做法,显然优化的余地已经很小了,因此我们考虑换一种思考方式。

首先对原题进行转化,即为 选取若干个二进制数,使他们或起来为全集 \(s\)

进一步对问题进行抽象,设 \(f_i\) 表示 选取若干个数或起来为状态 \(i\) 的方案数,用 dp 的方法进行转移,最后要求解的答案就是 \(f_s\)。如何计算 \(f_i\) 呢?这里就需要用到容斥进行巧妙地转移,我们设 \(g_i\) 表示选取若干个数或起来为状态 \(i\) 的子集的方案数,那么很显然,假如 \(i\)\(cnt_i\) 个子集(这里将 \(0\)(空集) 也视为子集),那么 \(g_i=2^{cnt_i}\),也就是其中这 \(cnt_i\) 个元素任意选或不选的结果,那么要求解 \(f_i\) 时只要乘以对应的容斥系数即可。特殊地,这题的容斥系数也就是以 “\(i\) 在二进制下与 \(s\) 相差的 \(1\) 的个数”为 \(-1\) 指数的那个数。

\(cnt\) (代码中为 \(f\))在读入时初始化,然后子集转移时用高维前缀和(sosdp)即可。时间复杂度 \(O(n+m\times 2^m)\)

代码如下:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1e9+7;
int n,m;
ll f[(1<<20)+5],ans;
ll two[1000005];
int count_1(int x)
{
	int cnt=0;
	while(x)
	{
		x&=(x-1);
		cnt++;
	}
	return cnt;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		int num,x,s=(1<<m)-1;
		scanf("%d",&num);
		while(num--)
		{
			scanf("%d",&x);
			s^=(1<<(x-1));
		}
		f[s]++;
	}
	for(int i=0;i<m;i++) for(int s=(1<<m)-1;s>=0;s--) if(s&(1<<i)) f[s^(1<<i)]=(f[s^(1<<i)]+f[s])%mod;
	two[0]=1;
	for(int i=1;i<=n;i++) two[i]=two[i-1]*2%mod;
	for(int i=0;i<(1<<m);i++)
	{
		int cnt=count_1(i);
		if(cnt%2==0) ans=(ans+two[f[i]])%mod;
		else ans=((ans-two[f[i]])%mod+mod)%mod;
	}
	printf("%lld\n",ans);
}
posted @ 2022-07-31 22:02  Freshair_qprt  阅读(51)  评论(0编辑  收藏  举报