目录
1.题目描述
农夫约翰和奶牛贝茜喜欢在业余时间互相出数学题。
约翰给贝茜出了一道相当难的问题,导致她没能解决。
现在,她希望通过给约翰出一道有挑战性的难题来报复他。
贝茜给了约翰一个表达式 (B+E+S+S+I+E)(G+O+E+S)(M+O+O),其中包含七个变量 B,E,S,I,G,O,M(O 是变量,不是零)。
对于每个变量,她给约翰一个列表,表中包含该变量可采用的最多 20 个整数值。
她要求约翰计算,共有多少种给变量赋值的方法可以使得表达式的计算结果为偶数。
输入格式
第一行包含一个整数 N。
接下来 N 行,每行包含一个变量和该变量的一个可能值。
每个变量至少出现 1 次,最多出现 20 次。
同一变量不会重复列出同一可能值。
输出格式
输出可以使得表达式的计算结果是偶数的给变量赋值的方法总数。
数据范围
7≤N≤140,
所有变量的可能取值范围 [−300,300]
本题答案不会超出int
范围。
输入样例:
10
B 2
E 5
S 7
I 10
O 16
M 19
B 3
G 1
I 9
M 2
输出样例:
6
样例解释
共有 6 种可能的赋值方式:
(B,E,S,I,G,O,M) = (2, 5, 7, 10, 1, 16, 19) -> 53,244
= (2, 5, 7, 10, 1, 16, 2 ) -> 35,496
= (2, 5, 7, 9, 1, 16, 2 ) -> 34,510
= (3, 5, 7, 10, 1, 16, 2 ) -> 36,482
= (3, 5, 7, 9, 1, 16, 19) -> 53,244
= (3, 5, 7, 9, 1, 16, 2 ) -> 35,496
注意,(2, 5, 7, 10, 1, 16, 19)
和 (3, 5, 7, 9, 1, 16, 19)
,虽然计算结果相同,但是赋值方式不同,所以要分别计数。
2.题意分析
题意:对输入的七个变量B,E,S,I,G,O,M的各种取值,进行组合,并代入题目给出的式子(B+E+S+S+I+E)(G+O+E+S)(M+O+O),若式子的结果为偶数,则符合要求,最终要求输出符合要求的赋值方式的个数
3.解题思路
思路1:因为式子的结果为奇数还是偶数,可以看作是对式子整体进行求余,若结果为0则式子的结果偶数,若为1则为奇数。根据性质1(详见附录),则可先将输入的各个变量的取值求余2的结果统计起来。然后采用二进制枚举的方法,确定7个变量的取值是1还是0,然后再将这七个变量的取值带入式子中去,若式子符合要求,则再计算一下该种取值的个数有多少种,并累加到最终结果中
思路2:因为式子的结果为奇数还是偶数,可以看作是对式子整体进行求余,若结果为0则式子的结果偶数,若为1则为奇数。根据性质1(详见附录),则可先将输入的各个变量的取值求余2的结果统计起来。然后再采用DFS的方法,每一步的状态为确定当前变量的取值,状态的选择有0和1可选,问题边界为式子的答案确认与否。通过这种方法逐步确定7个变量得取值是1还是0,根据性质1和2,我们可以在判断出部分乘数((MorG+O+E+SorB+I),式子化简过程详见附录末尾)为偶数后,确定含该种变量的取值都符合条件,则可直接计算该种类型的取值的个数有多少种,并累加到最终结果中。
4.代码实现,
思路1代码:
a[26][2]:存储输入的各个变量取值求余2后的两种取值0或者1的个数
d[7]:设定的7个变量的顺序
oor[10]:存储当前枚举次序的各个变量的取值
代码思路:因为题目中,输入的取值有可能是负数,所以先将输入的取值求绝对值,然后求余2,再存入对应的a[][]中。因为有7个变量需要确定,所以由0,枚举到(-1)即(1<<7-1)。然后通过(i>>j&1)的方式,将每次枚举的第j个变量的取值取出来,若a[d[j]-'A'][]==0,则代表第j个变量,并未有这种类型的取值,则break,否则将该种取值存入oor[j]中。当7个变量确定后,带入化简后的式子(化简过程详见附录末尾),若满足条件,则计算一下,该种类型的取值有多少种,累加到ans上。枚举完后输出ans。
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int a[26][2];
int n, ans = 0;
int oor[10];
char d[7] = { 'M','O','S','E','G','I','B' };
void check() {
int sum = (oor[6]+oor[5])*(oor[4]+oor[3]+oor[2]+oor[1])*oor[0];
if(sum%2==0){
sum=1;
for(int i=0;i<7;i++){
sum*=a[d[i]-'A'][oor[i]];
}
ans+=sum;
}
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
char c;
int za;
cin >>c >>za;
a[c - 'A'][abs(za)%2]++;
}
for(int i=0;i<(1<<7);i++){
for(int j=0;j<7;j++){
if(i>>j&1){
if(a[d[j]-'A'][1]==0) break;
oor[j]=1;
}
else {
if(a[d[j]-'A'][0]==0) break;
oor[j]=0;
}
if(j==6) check();
}
}
cout << ans << endl;
return 0;
}
思路2代码:
a[26][2]:存储输入的各个变量取值求余2后的两种取值0或者1的个数
st[26][2]:判断各个变量的该种取值是否使用
d[7] = { 'M','O','S','E','G','I','B' }:设定的7个变量的顺序,按部分来分,则有M,OSEG,BI,三个部分
oor[10]:存储当前枚举次序的各个变量的取值
代码思路:先对st和oor数组进行初始化,所以先将输入的取值求绝对值,然后求余2,再存入对应的a[][]中。然后开始dfs,因为初始时没有变量确定,故dfs(0)。在dfs()中,枚举变量t取0和1的两种情况,当该种情况没有被选时让oor[t]=0,或oor[t]=1,然后将该种情况标记一下,然后dfs(t+1)。当t==1,或t==5,t==7时,因为此时已经确定了式子的一部分,可以通过check()代入式子的一部分,来判断整个式子的奇偶,若符合条件,则计算一下,该种情况的个数然后累加到ans上,若不满足则继续枚举,或返回并恢复状态。
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
int a[26][2];
bool st[26][2];
char d[7] = { 'M','O','S','E','G','I','B' };
int n, ans = 0;
int oor[10];
bool check(int t) {
if (t == 7) {
int sum = (oor[6] + oor[5]);
if (sum % 2 == 0) {
sum = 1;
for (int i = 0; i < 7; i++) {
sum *= a[d[i] - 'A'][oor[i]];
}
ans += sum;
return true;
}
}
if (t == 5) {
int sum = (oor[4] + oor[3] + oor[2] + oor[1]);
if (sum % 2 == 0) {
sum = 1;
for (int i = 0; i < 5; i++) {
sum *= a[d[i] - 'A'][oor[i]];
}
for (int i = 5; i < 7; i++) {
sum *= (a[d[i] - 'A'][0] + a[d[i] - 'A'][1]);
}
ans += sum;
return true;
}
}
if (t == 1) {
int sum = (oor[0]);
if (sum % 2 == 0) {
sum = 1;
for (int i = 0; i < 1; i++) {
sum *= a[d[i] - 'A'][oor[i]];
}
for (int i = 1; i < 7; i++) {
sum *= (a[d[i] - 'A'][0] + a[d[i] - 'A'][1]);
}
ans += sum;
return true;
}
}
return false;
}
void dfs(int t) {
if ((t == 1 || t == 5 || t == 7) && check(t)) {
return;
}
int now = d[t] - 'A';
for (int j = 0; j < 2; j++) {
if (!st[now][j]) {
oor[t] = j;
st[now][j] = true;
dfs(t + 1);
st[now][j] = false;
oor[t] = -1;
}
}
}
int main() {
cin >> n;
memset(st, false, sizeof st);
memset(oor, -1, sizeof oor);
for (int i = 0; i < n; i++) {
char c;
int za;
cin >> c >> za;
a[c - 'A'][abs(za) % 2]++;
}
dfs(0);
cout << ans << endl;
return 0;
}
5.附录
性质1:各个变量先运算再求余2等价于,先对各个变量求余2再运算
性质2:乘法运算中含偶数参与,则结果为偶数
证明1:乘法:(a*b)%2=a%2*b%2。乘法运算中含偶数参与,则结果为偶数
a*b有三种情况
1.奇数*偶数
2.奇数*奇数
3.偶数*偶数
1.奇数*偶数
先运算再求余2
(2n+1)*2m=2(m*n+m),求余2得0。
先求余2再运算
1*0=0,故情况1符合预期
2.奇数*奇数
先运算再求余2
(2n+1)*(2m+1)=2(m*n+n+m)+1,求余2得1。
先求余2再运算
1*1=1,故情况2符合预期
3.偶数*偶数
先运算再求余2
(2n)*(2m)=2(n*m),求余2得0。
先求余2再运算
0*0=0,故情况3符合预期
证明1成立,性质2成立
证明2:加法:(a+b)%2=a%2+b%2。
1.奇数+偶数
2.奇数+奇数
3.偶数+偶数
1.奇数+偶数
先运算再求余2
(2n+1)+2m=2(n+m)+1,求余2得1。
先求余2再运算
1+0=1,故情况1符合预期
2.奇数+奇数
先运算再求余2
(2n+1)+(2m+1)=2(n+m+1),求余2得0。
先求余2再运算
1+1=0,故情况2符合预期
3.偶数+偶数
先运算再求余2
(2n)+(2m)=2(n+m),求余2得0。
先求余2再运算
0+0=0,故情况3符合预期
证明2成立
式子化简:
根据性质1,式子中只有乘法和加法运算,这两种运算经证明,都可以先求余2再运算,故该式子可以先进行求余2,再运算。
则有,
(B+E+S+S+I+E)*(G+O+E+S)*(M+O+O)%2
=(B+I+S*2+E*2)*(G+O+E+S)*(M+2*O)%2
=(B%2+I%2)*(G%2+O%2+E%2+S%2)*M%2
整个式子,由三个部分(B%2+I%2)*(G%2+O%2+E%2+S%2)*M%2相乘而得,根据性质2,只要这三个部分有一个为偶数,则整个式子的结果也为偶数。