团队训练(二)

团队训练(二)-- 二进制枚举 && 位运算

知识点总结:

  • A & B(与运算)
    特殊1 & X,可判断整数的奇偶性。

  • A | B(或运算)

  • A ^ B(异或运算)
    用途:A ^ A = 0,A ^ A ^ A = A,A ^ A ^ B = B,可判断字符出现次数或者字符串是否相同。

  • A << B(移位)
    用途:1 << n = 2 ^ n,做乘二运算。

  • 骚操作:

    1. n & (n - 1) :消除n的二进制中最后一个1。如:3 & 2 = 2.
    2. 利用异或操作和空格进行英文字符大小写互换,如:('z' ^ ' ') = 'Z',('Z' ^ ' ') = 'z'.
  • 二进制枚举板子:

for(int i = 0; i < (1<<n); i++) //从0~2^n-1个状态
    {
        for(int j = 0; j < n; j++) //遍历二进制的每一位
        {
            if(i & (1 << j))//判断二进制第j位是否存在
            {
                printf("%d ",a[j]);//如果存在输出第j个元素
            }
        }
        printf("\n"); 
    }

解析:i是枚举n的子集个数,(1 << j),相当于一个哨兵在巡逻。举个栗子:如n = 3,我们输出的是1,2,(1,2),3,(1,3),(2,3),(1,2,3).我们不难发现规律,1 << 0 = 1,转换成二进制也就是001,而当i = 1时,转换成二进制也是001,那么这时候 001 & 001 = 1,即为真,接着到i = 2, 1 << 1 = 2,转换成二进制则 010 & 010 = 2,即为真,接着会到i = 3,此时转换成二进制为011,那么1 << 0 -> 001 ,1 << 1 -> 010,将他们进行与运算都可得到不为0的值(true),也就枚举出a[0],a[1],接下来依次类推。
表格说明:

例题:

1.teacher Li
题目

This time,suddenly,teacher Li wants to find out who have missed interesting DP lesson to have fun.The students who are found out will get strictly punishment.Because,teacher Li wants all the students master the DP algorithm.
However,Li doesn't want to waste the class time to call over the names of students.So he let the students to write down their names in one paper.To his satisfaction,this time, only one student has not come.
He can get the name who has not come to class,but,it is troublesome,and,teacher always have many things to think about,so,teacher Li wants you, who is in the ACM team, to pick out the name.
Input
There are several test cases.The first line of each case have one positive integer N.N is the number of the students,and N will not greater than 500,000.
Then,following N lines,each line contains one name of students who have attended class.The N-1 lines are presented after N lines.These N-1 lines indicates the names of students who have come to class this time,one name in one line.
The length of student's name is not greater than 30.
Process to the end of file.
Output
For each test case, first print a line saying "Scenario #k", where k is the number of the test case.Then output the name of the student who have not come to class.One case per line.Print a blank line after each test case, even after the last one.

Sample Input
3
A
B
C
B
C

Sample Output
Scenario #1
A

解析:其实题目就是问那个字符串/字符仅仅出现了一次,直接异或即可,还有就是注意一下输出格式。

AC代码:

#include<stdio.h>
#include<iostream>
#include<vector>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int num = 35;
const long long mod = 1e9 + 7;
const long long inf = 0x3f3f3f3f;
int n, cnt; 
char str[num], temp[num];
int main()
{
	while(~scanf("%d",&n))
	{
		cnt ++;
		memset(str,0,sizeof(str));
		scanf("%s",str);
		for(int i = 1; i < 2 * n - 1; i++)
		{
			scanf("%s",temp);
			int len = strlen(temp);
			for(int j = 0; j < len; j++)
				str[j] = str[j] ^ temp[j];
		}
		printf("Scenario #%d\n",cnt);
		printf("%s\n",str);
		printf("\n");
	}
	return 0;
}

2.Find different
题目

Give an odd number n, (1<=n<=10000001)

Given you an array which have n numbers : a[1], a[2] a[3] ... a[n].They are positive num.
There are n/2 numbers which appear twice and only one number appears once.
Now you should tell me the number which appears only once.
Input
There are several test cases end with EOF.
The first line there is a integer n.
Then the 2nd line there are n integer a[1],a[2]..a[n].
Output
For each case, output the number which appears only once.

Sample Input
7
3 2 7 2 1 7 3
1
7
11
1 1 2 2 3 3 4 4 5 5 9

Sample Output
1
7
9

备注:
数据量较大,建议用scanf读入

解析:该题题意为寻找仅仅出现一次的数字,也是异或就完事,假如n的范围小一些可以直接遍历,时间复杂度(n + maxn)。

AC代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 1e7 + 3;
const long long mod = 1e9 + 7;
const long long inf = 0x3f3f3f3f;
int n, x;
int main()
{
	while(~scanf("%d",&n))
	{
		int ans = 0; // 0 ^ x = x 
		while(n --)
		{
			scanf("%d",&x);
			ans = ans ^ x;
		}
		printf("%d\n",ans);
	}
	return 0;
}

3.和为K
题目
给出长度为n的数组,求能否从中选出若干个,使他们的和为K.如果可以,输出:Yes,否则输出No
Input
第一行:输入N,K,为数组的长度和需要判断的和(2<=N<=20,1<=K<=10^9)
第二行:N个值,表示数组中元素的值(1<=a[i]<=10^6)
Output
输出Yes或No

Sample Input
5 13
2 4 6 8 10

Sample Output
No

解析:二进制枚举,将子元素依次累加,若存在和为k,标记一下flag,然后输出Yes,假如没有和为K的子集,flag为初始值0,输出No.

AC代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const int num = 25;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
int n, k;
int a[num];

int main()
{
	while(~scanf("%d%d",&n, &k))
	{
		memset(a, 0, sizeof(a));
		int flag = 0;
		for(int i = 0; i < n; i++)
			scanf("%d", &a[i]);
		for(int i = 0; i < (1 << n); i++)
		{
			int ans = 0;
			for(int j = 0; j < n; j++)
			{
				if(i & (1 << j))
					ans += a[j];
			}
			if(ans == k)
			{
				flag = 1;
				break;
			}
		}
		if(flag)
			printf("Yes\n");
		else
			printf("No\n");
	}
	return 0;
}

4.趣味解题
题目

ACM程序设计大赛是大学级别最高的脑力竞赛,素来被冠以"程序设计的奥林匹克"的尊称。大赛至今已有近40年的历史,是世界范围内历史最悠久、规模最大的程序设计竞赛。比赛形式是:从各大洲区域预赛出线的参赛队伍,于指定的时间、地点参加世界级的决赛,由1个教练、3个成员组成的小组应用一台计算机解决7到13个生活中的实际问题。
现在假设你正在参加ACM程序设计大赛,这场比赛有 n 个题目,对于第 i 个题目你有 a_i 的概率AC掉它,如果你不会呢,那么这时候队友的作用就体现出来啦,队友甲有 b_i 的概率AC掉它, 队友乙有 c_i 的概率AC掉它,那么现在教练想知道你们队伍做出 x 个题目的概率。

Input
输入一个正整数T(T<=100),表示有T组数据,对于每组数据首先输入一个 n (7<=n<=13),表示有 n 个题目,接下来输入三行,
第一行输入 n 个数a_i,第二行输入 n 个数b_i,第三行输入 n 个数c_i, 其中 a_i, b_i, c_i 的意义如题,最后输入一个 x 表示教练想要知道你们队伍做出的题目数(x>=0)。
Output
输出一行表示结果,保留4位小数

Sample Input
2
7
0.1 0.2 0.3 0.4 0.5 0.6 0.7
0.2 0.3 0.4 0.5 0.6 0.7 0.8
0.3 0.4 0.5 0.6 0.7 0.8 0.9
1
7
0.1 0.2 0.3 0.4 0.5 0.6 0.7
0.2 0.3 0.4 0.5 0.6 0.7 0.8
0.3 0.4 0.5 0.6 0.7 0.8 0.9
5

Sample Output
0.0000
0.2811

解析:这道题刚开始没写出来,主要是高中数学算概率的操作真的忘了,然后队友吹了一波怎么算概率两个人今早就同时AC掉了,我我我我数学真的太菜了。
二进制解法:假设pi是AC掉第i道题的概率,qi是WA掉第i道题的概率,那么解出第1道题的概率(假设总共3道题,实际不可能),做出一道题的概率P:p1 * q2 * q3 + p2 * q1 * q3 + p3 * q1 * q2.其次讲一下每道题可能AC的概率就是(1-该道题WA的概率),该道题WA的概率:(1 - 队友甲AC的概率) * (1 - 队友乙AC的概率) * (1 - 队友丙AC的概率),然后再二进制枚举,加个计数判断一下是否与x相等即可。
概率dp解法: dp[i][j] 表示前i道题(包括第i道)ACj道的概率(时间比二进制枚举快五倍).
方程如下:

  • dp[0][0] = 1.
  • j == 0,dp[i][j] = dp[i-1][j] * q[i].
  • j != 0,dp[i][j] = dp[i-1][j] * q[i] + dp[i-1][j-1] * p[i].
    最后加一个x > n的特判就行(坑点).

二进制解法:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const int num = 15;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
double p[num], q[num]; // 每一道题的AC率与WA率 
double w[4][num]; //存放每个队友每场比赛概率 
int t, n, x;
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		double ans = 0; //重新初始化为0 
		memset(p, 0, sizeof(p));
		memset(q, 0, sizeof(q));
		memset(w, 0, sizeof(w));
		scanf("%d", &n);
		for(int i = 0; i < 3; i++)
			for(int j = 0; j < n; j++)
				scanf("%lf", &w[i][j]);
		scanf("%d", &x);
		for(int i = 0; i < n; i++)
		{
			q[i] = (1 - w[0][i]) * (1 - w[1][i]) *(1 - w[2][i]);
			p[i] = 1 - q[i];
		}
		for(int i = 0; i < (1 << n); i++)
		{
			int cnt = 0;
			double temp = 1.0;
			for(int j = 0; j < n; j++)
			{
				if(i & (1 << j))
				{
					temp = temp * p[j];
					cnt ++;
				}
				else
					temp = temp * q[j];
			}
			if(cnt == x)
			{
				ans += temp;
			}
		}
		printf("%.4lf\n", ans);
	}
	return 0;
}

概率dp解法:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const int num = 15;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
double p[num], q[num]; // 每一道题的AC率与WA率 
double w[5][num]; //存放每个队友每场比赛概率 
double dp[num][num]; //前i道题ACj道的概率 (包括第i道)
int t, n, x;
int main()
{
	scanf("%d", &t);
	while(t--)
	{
		double ans = 0; //重新初始化为0 
		memset(p, 0, sizeof(p));
		memset(q, 0, sizeof(q));
		memset(w, 0, sizeof(w));
		memset(dp, 0, sizeof(dp));
		scanf("%d", &n);
		for(int i = 1; i <= 3; i++)
			for(int j = 1; j <= n; j++)
				scanf("%lf", &w[i][j]);
		scanf("%d", &x);
		for(int i = 1; i <= n; i++)
		{
			q[i] = (1 - w[1][i]) * (1 - w[2][i]) *(1 - w[3][i]);
			p[i] = 1 - q[i];
		}
		dp[0][0] = 1; //设置一个值为1,避免使dp递推始终为0. 
		for(int i = 1; i <= n; i++)
		{
			for(int j = 0; j <= i; j++)
			{
				if(j == 0) //到目前为止一题都未AC 
					dp[i][j] = dp[i-1][j] * q[i];
				else //前i道ACj道的概率 = 前i-1道ACj道的概率乘这道WA的概率 + 前i-1道ACj-1道的概率乘这道AC的概率 
					dp[i][j] = dp[i-1][j] * q[i] + dp[i-1][j-1] * p[i];
			}
		} 
		if(x > n) //特判x > n的情况 
			printf("0.0000\n");
		else
			printf("%.4lf\n", dp[n][x]);
	}
	return 0;
}

5.陈老师加油
题目

陈老师经常开车在哈尔滨的大街上行走,假设刚开始油箱里有T升汽油,每看见加油站陈老师就要把汽油的总量翻倍(就是乘2);每看见十字路口气油就要减少1升;最后的时候陈老师的车开到一个十字路口,然后车就没油了------就熄火了,陈老师好痛苦啊~~~!
然后他就开始回忆,一路上一共遇到5个加油站,10个十字路口,问造成这种惨烈的境遇有多少种可能?

Input
输入一个T ,(1<=T<=100);
Output
输出可能的方案数。
Sample Input
1

Sample Output
10

解析:二进制枚举法一:二进制枚举经过加油站为1,经过十字路口为0,可以枚举15种情况,当油没了break一下出来进行判断此时是否经过五个加油站和经过十个十字路口,这样就不会出现最后一个可能经过加油站的可能。
二进制枚举法二:也可枚举14种情况,剩1L的油留给经过最后一个十字路口。
Dfs解法:要么经过加油站,要么经过十字路口,两种走法,自己乱搞一下就可以了,嘻嘻(时间比二进制枚举快三倍)。

二进制枚举法一:

#include <bits/stdc++.h>
using namespace std;
int main()
{
    int t,tmp,ans,k1,k2,i,j;
    cin>>t;
    ans=0;
    for(i=0;i<(1<<15);i++)
    {
        tmp=t;
        k1=k2=0;
        for(j=0;j<15;j++)
        {
            if(i&(1<<j))
            {tmp=tmp*2;k1++;}
            else
            {
                tmp--;k2++;
                if(tmp==0) break; //!important
            }
        }
        if(k1==5&&k2==10&&tmp==0)ans++;
    }
    printf("%d\n",ans);
    return 0;
}

二进制枚举法二:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const int num = 15;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
int cnt = 0;

int main()
{
	int t, n1 = 0, n2 = 0; //油的体积,经过加油站数量,经过十字路口数量 
	scanf("%d", &t);
	for(int i = 0; i < (1 << 14); i++)
	{
		int temp = t;
		n1 = 0, n2 = 0;;
		for(int j = 0; j < 14; j++)
		{
			if(i & (1 << j))
			{
				n1 ++;
				temp = temp * 2;
			}
			else
			{
				n2 ++;
				temp--;
				if(temp == 0)
					break;
			}
			if(temp == 1 && n1 == 5 && n2 == 9) //剩1L的油给最后一次经过一个十字路口 
				cnt ++;
		}
	}
	printf("%d", cnt);
	return 0;
}

dfs解法:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const int num = 15;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
int cnt = 0;
void dfs(int t, int n1, int n2)
{
	if(n1 == 5 && n2 + 1 == 10 && t - 1 == 0)
	{
		cnt ++;
		return;
	}
	if(n2 == 10 || t == 0) //判断当前是否已经路过10个十字路口/油已耗完(可行性剪枝) 
		return;
	else if(n1 == 5) //如果已经路过5个加油站那么只能走十字路口 
		dfs(t-1, n1, n2 + 1);
	else //1.走十字路口; 2.走加油站. 
	{
		dfs(t-1, n1, n2 + 1);
		dfs(t * 2, n1 + 1, n2);
	}
}
int main()
{
	int t, n1 = 0, n2 = 0; //油的体积,经过加油站数量,经过十字路口数量 
	scanf("%d", &t);
	dfs(t, n1, n2);
	printf("%d", cnt);
	return 0;
}

6.纸牌游戏
题目
给你一些扑克,每张都对应一个点数,分别对应1-13,K 就是13;J 是11;Q是12;
现在想从这些扑克牌中取出一些牌,让这些牌的点数的和等于一个幸运数值P,问有多少种方案?
Input
输入数据第一行为n和p,分别代表n张扑克牌和幸运数(1<=n<=20,p<=260)
接下来是这n张牌的点数; 1<=点数<=13;
Output
输出能得到P 的方案数?

Sample Input
5 5
1 2 3 4 5

Sample Output
3

解析:二进制枚举解法:有点像和为K那道题的变型,也就是枚举求和就完事。
dfs解法:两种走法,要么选择加入下一张牌的点数,要么选择不加。
二进制枚举法:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const int num = 25;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
int a[num];
int n, cnt = 0, p;
int main()
{
	scanf("%d%d", &n, &p);
	for(int i = 0; i < n; i++)
		scanf("%d", &a[i]);
	for(int i = 0; i < (1 << n); i++)
	{
		int temp = 0;
		for(int j = 0; j < n; j++)
		{
			if(i & (1 << j))
				temp += a[j];
		}
		if(temp == p)
			cnt ++; 
	}
	printf("%d",cnt);
	return 0;
}

dfs解法:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
int n, p, cnt;
int a[25];
void dfs(int val, int k)
{
	if(k == n + 1)
	{
		if(val == p)
			cnt++;
		return;
	}
	dfs(val, k + 1); //选择不加a[k] 
	dfs(val + a[k], k + 1); //选择加a[k] 
}

int main()
{
	scanf("%d%d", &n, &p);
	for(int i = 1; i <= n; i++)
		scanf("%d", &a[i]);
	dfs(0, 1); //点数,当前遍历牌数 
	printf("%d", cnt);
	return 0;
}

7.四糸乃买花
题目

商店里有n朵花,四糸乃有w元钱。n朵花各不相同,四糸乃要买这n朵花其中的其中的若干朵。由于四糸乃很喜欢4这个数字,所以她希望她买的花的朵数是4的倍数,并且买完花后剩下的钱(可以为0)也是4的倍数。此外,因为已经来到花店了,所以四糸乃不能一朵花也不买。因为花店接下来还要做生意,四糸乃也不能将这n朵花全部买走。那么四糸乃一共有多少种买花方案呢?

Input
单组输入
测试数据共三行。
第一行是一个整数n表示有n朵花。(2<=n<=22)
第二行有n个整数,分别表示这n朵花的价格a[i]。(1<=a[i]<=1e7)
第三行是一个整数w代表四糸乃开始时持有的钱数(1<=w<=1e8)
Output
输出买花的方案数,输出占一行。

Sample Input
5
5 6 1 3 4
39

Sample Output
1

备注:
只有一种方案,买第1,2,3,4朵花,买的朵数4是4的倍数且不为0不为n,剩余钱数24是4的倍数,符合题意。因为题目中只有这一种方案符合题意,所以答案是1

解析:二进制枚举,就判断条件多了一些,剩余的钱数必须大于或等于0并且是4的倍数,购买花的数量为4的倍数,购买花的数量不能为0,也不能为n。
AC代码:

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<stdlib.h>
#include<string.h>
#include<math.h>
using namespace std;
typedef long long LL;
const int maxn = 1e5 + 7;
const int maxm = 2e5 + 7;
const long long inf = 0x3f3f3f3f;
const long long mod = 1e9 + 7;
int n, w, num = 0;
int a[25];
int main()
{
	scanf("%d", &n);
	for(int i = 0; i < n; i++)
		scanf("%d", &a[i]);
	scanf("%d", &w);
	for(int i = 0; i < (1 << n); i++)
	{
		int cnt = 0, temp = 0;
		for(int j = 0; j < n; j++)
		{
			if(i & (1 << j))
			{
				cnt ++; //买花的数量 
				temp += a[j]; //买花的总花费 
			}
		}
		// 剩余钱数应大于等于0 
		if(cnt % 4 == 0 && (w - temp) % 4 == 0 && cnt > 0 && cnt != n && (w - temp) >= 0)
				num ++; //方案数 
	}
	printf("%d", num);
	return 0;
}

猜你喜欢

转载自www.cnblogs.com/K2MnO4/p/12640178.html