问题描述
小蓝有一个长度为 N 的数组 A=[A0,A1,...,AN−1]A=[A0,A1,...,AN−1]。现在小蓝想要从 A 对应的数组下标所构成的集合 I=0,1,2,...,N−1 中找出一个子集 R1 ,那么 R1 在 I 中的补集为 R2。记 S1=∑r∈R1Ar,S2=∑r∈R2Ar,我们要求 S1 和 S2 均为偶数,请问在这种情况下共有多少种不同的 R1 。当 R1 或 R2 为空集时我们将 S1 或 S2 视为 0。
输入格式
第一行一个整数 T,表示有 T 组数据。
接下来输入 T 组数据,每组数据包含两行:第一行一个整数 N,表示数组 A 的长度;第二行输入 N 个整数从左至右依次为 A0,A1,...,AN−1相邻元素之间用空格分隔。
输出格式
对于每组数据,输出一行,包含一个整数表示答案,答案可能会很大,你需要将答案对 1000000007进行取模后输出。
样例输入
2
2
6 6
2
1 6
样例输出
4
0
样例说明
对于第一组数据,答案为 4。(注意:大括号内的数字表示元素在数组中的下标。)
R1=0,R2=1;此时 S1=A0=6为偶数, S2=A1=6为偶数。
R1=1,R2=0 ;此时 S1=A1=6为偶数, S2=A0=6为偶数。
R1=0,1,R2=;此时 S1=A0+A1=12为偶数, S2=0为偶数。
R1=,R2=0,1;此时 S1=0 为偶数, S2=A0+A1=12为偶数。
对于第二组数据,无论怎么选择,都不满足条件,所以答案为 00。
题目的分析:
题目的意思是对于一个集合I,把集合I分成两个子集R1和R2,并且这两个子集中元素的和都是偶数,对于空集,我们认为集合的值为0,子集元素的和为偶数。
要想分为两个偶数,我们先要知道怎么才会产生偶数。
奇数+奇数=偶数、偶数+偶数=偶数、奇数+偶数=奇数
因此,分为下列的两种种情况:
(1)若整个集合I的和为奇数,那么这个集合就无法拆分为两个偶数集合的和,此时只需要输出0就可以。在这个问题中,也就是如果集合中出现的奇数个数为奇数,那么这个集合就无法划分为两个偶数的和。
(2)集合I的和为偶数,可能是偶数+偶数产生的,也可能是偶数加奇数产生的,因此我们的选择方式分为两种。一种是选择全部是偶数的子集,另一种是选择偶数个奇数的子集。
假设数组A中,偶数元素的个数为even个,奇数元素个数为odd个。
(1)选择的是偶数作为子集,由于偶数+偶数=偶数,因此另一个子集和一定也是偶数。我们需要在even个偶数中选择0,1,2,3,4,……个,这里我们用到了数学中的二项式定理。
二项式定理如下:
公式中的n就是even,我们的取值是0,1,2,3,……,even-1,even。此时我们的a与b都是1,因此二项式系数和=2^(even)。可能的方案数就是 2^(even)。
(2)选择偶数个奇数作为子集,那么我们选择的个数应该是0,2,4,6,……,此时公式应当为二项式系数和的一半(因为我们只选取了偶数个,奇数个我们没有计算,对比全部的项,我们现在是全部项数的一半),因此此时的方案数就是 2^(odd-1)。【这里的odd一定是偶数,因为奇数个奇数我们已经在上面讨论了,结果为0 】
总的可能方案数=偶数方案数+偶数个奇数方案数。
代码如下:
#include <iostream>
#include <math.h>
using namespace std;
int main() {
int T,x,even,odd; //even是偶数,odd是奇数
long sum; //统计方案数目
long es=1,os=1;//用来求2^偶数 和 2^(奇数-1)
int mod = 1000000007;
scanf("%d",&T);
while(T--)
{
int n;
cin>>n;
even = 0;//记录偶数个数,每次使用前初始化为0
odd = 0;//记录奇数个数,每次使用前初始化为0
for(int i=0;i<n;i++){
cin>>x; //输入每个元素,并判断奇偶性
if(x%2==0)
{
even++;
}
else
{
odd++;
}
}
if(odd%2){ //如果奇数的个数为奇数个,整个集合肯定无法划分为两个偶数子集,输出0
cout<<0<<'\n';
}
else{
es = 1; os = 1;
for(int i=0;i<odd-1;i++)
{
os = (os*2)%mod; //求奇数方案
}
for(int j=0;j<even;j++)
{
es = (es*2)%mod; //求偶数方案
}
sum = (os*es)%mod; //所有可能的方案数
cout<< sum<<'\n';
}
}
return 0;
}