2014编程之美初赛第二场1003 集合

题目链接:集合

时间限制:12000ms
单点时限:6000ms
内存限制:256MB

 

描述

统计满足下列条件的集合对(A, B)的数量:

  • A,B都是{1, 2, …, N}的子集;

  • A,B没有公共的元素;

  • f(A)<= f(B)f(S)定义为S中所有元素的按位异或和。例如, f({}) = 0, f({1, 3}) = 2。

因为答案可能很大,你只需要求出它除以M的余数。


输入

第一行一个整数T (1 ≤ T ≤ 10),表示数据组数。

接下来是T组输入数据,测试数据之间没有空行。

每组数据格式如下:

仅一行,2个整数N和M (1 ≤ M ≤ 108)。


输出

对每组数据,先输出“Case x: ”,然后接一个整数,表示所求的结果。


数据范围

小数据:1 ≤ N ≤ 20

大数据:1 ≤ N < 212



样例输入
1
3 100000000
样例输出
Case 1: 18

 

本质:

组合数学+DP

 

思路:

首先,所有的分组情况是3^n(每个元素可以放在第一个集合,也可以放在第二个集合,也可以抛弃),对于每一种情况,把异或值小的放在左边,异或值大的放在右边,都能满足情况,那么有(3^n)/2种情况,但是左右两边异或值相同的时候可以计算两次,所以还要加上相等情况数的一半,左右两边异或相等的时候,那么左右两个集合的并集的异或值为0,那么我们需要知道1~n中有多少组数异或为0的情况,并且如果找到一组k个数字异或为0,那么现在将对这组数字进行分配:把0个元素分配到左边的集合:C(k,0)、把1个元素分配到左边的集合:C(k,1)...把k个元素分配到左边的集合:C(k,k),总共的情况数是2^k,那么我们计算的时候就可以用DP进行递推了

dp[i][j]代表前i个数分配好了,两个集合的异或值为j的方案数

有转移:

dp[i+1][j]+=dp[i][j];

dp[i+1][j^(i+1)]+=dp[i][j]*2;//多一个数,2^k中k+1,那么方案数要乘以2

所以最后的答案就等于(3^n+dp[n][0])/2

由于过程要求余m,但是最后结果的时候要除以2,从Logic_IU得知了一个比较好的处理办法,求余2m即可,妙哉

 

#include<stdio.h>
#include<string.h>
long long int dp[4500][4500];//dp[i][j]代表前i个数分配好了,异或值为j的方案数
long long int num[20]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095};
int main()
{
	long long int I,T,n,MOD,i,j,ans,m,MOD2,t;
	scanf("%lld",&T);
	for(I=1;I<=T;I++)
	{
		scanf("%lld%lld",&n,&MOD);
		MOD2=MOD*2;
		ans=1;
		for(i=1;i<=n;i++)
			ans=(ans*3)%MOD2;
		for(i=1;i<=12;i++)//找到1~n异或的上限
			if(n<=num[i])
				break;
		m=num[i];
		memset(dp,0,sizeof(dp));
		dp[0][0]=1;
		for(i=0;i<n;i++)
			for(j=0;j<=m;j++)
			{
				t=j^(i+1);
				dp[i+1][j]=(dp[i+1][j]+dp[i][j])%MOD;
				dp[i+1][t]=(dp[i+1][t]+dp[i][j]*2)%MOD;
			}
		ans=(ans+dp[n][0])%MOD2;
		ans/=2;
		ans%=MOD;
		printf("Case %lld: %lld\n",I,ans);
	}
	return 0;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值