HNOI2011 卡农

题目链接:戳我

蒟蒻今天正式开始学容斥了!!(但是还是不会做啊不会做啊不会做啊)

这个题题意化简下来是这样的:n个数中选择m个子集,它们需要满足三个条件——
1、选择的子集不能为空
2、选择的子集不能相同
3、选择的子集中,每个数出现的次数必须为偶数
那个集合相同的条件没有什么难搞的,直接算出来答案之后,除以m!即可(因为有m!种排列顺序嘛)

然后还有一种题意解释:
\(2^n-1\)个数中,选择m个数,使得它们的异或和为0;(限制条件参考上面三个)

我们设\(dp[i]\)表示选择i个数,它们的异或和为0。

然后因为第三个条件,所以我们知道如果确定了前i-1个数,那么第i个也就确定下来了(选择原先出现的奇数次的数即可),所以总的选择种类为\(A_{2^n-1}^{i-1}\)

但是这样的话里面存在不合法情况。第一种这一次选择的子集为空了。那么这样的话表现出来的就是原先i-1个数的异或和为0。所以我们要减去\(dp[i-1]\)

第二种是选择的子集相同了。我们枚举这次选择的数(和前面有一个数重复了),这个数到底是什么。因为有一个数重复了,所以剩下还有i-2个数要在\(2^n-1\)个里面进行选择,这样的话一共有\(2^n-1-(i-2)\)种数可以选择。当然我们也要考虑到到底和前面哪一个数重复了(也就是这个重复的数放到哪里),所以要乘上\((i-1)\),因为重复了,两次异或等于没有操作,所以i-2个数的异或和是0,所以最后乘上\(dp[i-2]\)

所以最后的递推式就是\(dp[i]=A_{2^n-1}^{i-1}-dp[i-1]-dp[i-2]*(i-1)*(2^n-1-(i-2))\)

至于为什么对\(dp[0]=1,dp[1]=0\)这样子初始化。大家可以考虑手算一下n=3,n=2的dp值,然后根据答案以及系数就可以推出来dp[0]和dp[1]了。

代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define MAXN 1000010
#define mod 100000007
using namespace std;
int n,m;
long long k=1;
long long dp[MAXN],A[MAXN];
inline long long pow(int x,int y)
{
	if(y==0) return 1;
	long long cur_ans=1;
	while(y)
	{
		if(y&1) cur_ans=1ll*cur_ans*x%mod;
		x=1ll*x*x%mod;	
		y>>=1;
	}
	return cur_ans;
}
int main()
{
	#ifndef ONLINE_JUDGE
	freopen("ce.in","r",stdin);
	#endif
	scanf("%d%d",&n,&m);
	long long maxx=pow(2,n)-1;
	maxx%=mod;
	A[1]=maxx;
	dp[0]=1,dp[1]=0;
	for(int i=2;i<=m;i++) A[i]=1ll*A[i-1]*(maxx-i+1)%mod;
	for(int i=1;i<=m;i++) k=1ll*k*pow(i,mod-2)%mod;
	for(int i=2;i<=m;i++)
		dp[i]=(A[i-1]-dp[i-1]-((1ll*dp[i-2]*(i-1)%mod)*(maxx-(i-2)))%mod+mod)%mod;
	printf("%lld\n",1ll*dp[m]*k%mod);
	return 0;
}
posted @ 2019-02-21 20:30  风浔凌  阅读(148)  评论(0编辑  收藏  举报