- 选数——组合问题
题目描述
已知 n 个整数 x1,x2,…,xn ,以及11个整数k(k<n)。从n个整数中任选k个整数相加,可分别得到一系列的和。例如当n=4,k=3,4个整数分别为3,7,12,19时,可得全部的组合与它们的和为:
3+7+12=223+7+12=22
3+7+19=293+7+19=29
7+12+19=387+12+19=38
3+12+19=343+12+19=34。
现在,要求你计算出和为素数共有多少种。
例如上例,只有一种的和为素数:3+7+19=29
输入格式
n,k(1 ≤ n ≤ 20,k<n)
x1,x2,…,xn (1 ≤ xi ≤ 5000000)
输出格式
1个整数(满足条件的种数。
输入
4 3
3 7 12 19
输出
1
#include<iostream>
using namespace std;
const int N = 20;
int n, k, res;
int a[N], path[N]; // path[i]存储路径上选择的数
// 函数功能: 判断x是否为素数
bool judge(int x)
{
if(x == 2) return true;
if(x % 2 == 0) return false;
for(int i = 3; i * i <= x; i += 2)
{
if(x % i == 0) return false;
}
return true;
}
void dfs(int u, int st)
{
// 递归终止条件
if(u == k)
{
int sum = 0;
for(int i = 0; i < k; i++) sum += path[i];
if(judge(sum)) res++;
return;
}
// 每次选择比前一个数大的数
for(int i = st; i < n; i++)
{
path[u] = a[i];
dfs(u+1, i+1);
}
}
int main()
{
cin >> n >> k;
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
dfs(0, 0);
printf("%d", res);
return 0;
}
解题思路:之前学过的模板是解决排序问题,而这个是组合问题。dfs函数中的递归终止条件相似都是当u=n(k)时终止,不一样的是填每个数时的条件。排序的条件是只要备选的数中未被标记的数就行,但是组合的条件是不能与之前的分支有重复的数。通过有序的列举所有的情况发现只要后面的数大于前面的数就不会出现重复,也可以说在所给备选序列中按照保序的方式取出k个数,那么每次取出的情况就不会发生重复。所以设置一个参数st,表示第u层从备选序列可以选择的开始位置,保证递归过程中保序。
- 产生数
题目描述
一个N×M的由非负整数构成的数字矩阵,你需要在其中取出若干个数字,使得取出的任意两个数字不相邻(若一个数字在另外一个数字相邻8个格子中的一个即认为这两个数字相邻),求取出数字和最大是多少。
输入格式
第1行有一个正整数T,表示了有T组数据。
对于每一组数据,第一行有两个正整数N和M,表示了数字矩阵为N行M列。
接下来N行,每行M个非负整数,描述了这个数字矩阵。
输出格式
T行,每行一个非负整数,输出所求得的答案。
数据范围
N, M≤6,T≤20
输入
3
4 4
67 75 63 10
29 29 92 14
21 68 71 56
8 67 91 25
2 3
87 70 85
10 3 17
3 3
1 1 1
1 99 1
1 1 1
输出
271
172
99
#include<iostream>
#include<cstring>
using namespace std;
int a[6][6]; // 储存数组
int n, m, t;
int ans, sum;
bool mark[6][6]; // 标记是否使用过
void dfs(int x, int y)
{
// 递归终止条件, 当最后一行搜索完后,更新答案
if(x == n)
{
ans = max(ans, sum);
return;
}
// 当搜索完最后一列后,搜索下一行
if(y == m)
{
dfs(x+1, 0);
return;
}
int dx[9] = {-1, -1, -1, 0, 0, 0, 1, 1 ,1};
int dy[9] = {-1, 0, 1, -1, 0, 1, -1, 0, 1};
// 搜索x, y周围的数及(x, y)是否被标记过
int flag = 1;
for(int i = 0; i < 9; i++)
{
int x1 = x + dx[i], y1 = y + dy[i];
if(x1 >= 0 && x1 < n && y1 >= 0 && y1 < m && mark[x1][y1])
{
flag = 0; // 如果(x, y) 周围或者 (x, y)本身被标记过,flag变为0
}
}
// 如果未被标记则深搜下一个数
if(flag)
{
sum += a[x][y];
mark[x][y] = true;
dfs(x, y+1);
mark[x][y] = false;
sum -= a[x][y];
}
}
int main()
{
cin >> t;
while(t--)
{
cin >> n >> m;
for(int i = 0; i < n; i++)
for(int j = 0; j < m; J++)
cin >> a[x][y];
memset(mark, false, sizeof(mark));
sum = 0. ans = 0;
dfs(0, 0);
printf("%d\n", ans);
}
return 0;
}
解题思路:每次选择都会影响下一次的选择的结果,所以选择dfs。顺次搜索每一个元素,判断该元素的周围元素是否被标记过,如果没被标记就把答案加上这个数,否则搜索下一个数。递归终止条件是搜索到最后一行后更新ans返回。
- 海战——搜索连通块的数量
题目描述
在峰会期间,武装部队得处于高度戒备。警察将监视每一条大街,军队将保卫建筑物,领空将布满了F-2003飞机。此外,巡洋船只和舰队将被派去保护海岸线。不幸的是因为种种原因,国防海军部仅有很少的几位军官能指挥大型海战。因此,他们考虑培养一些新的海军指挥官,他们选择了“海战”游戏来帮助学习。
在这个著名的游戏中,在一个方形的盘上放置了固定数量和形状的船只,每只船却不能碰到其它的船。在这个题中,我们仅考虑船是方形的,所有的船只都是由图形组成的方形。编写程序求出该棋盘上放置的船只的总数。
输入格式
输入文件头一行由用空格隔开的两个整数R和C组成,1<=R,C<=1000,这两个数分别表示游戏棋盘的行数和列数。接下来的R行每行包含C个字符,每个字符可以为“#”,也可为“.”,“#”表示船只的一部分,“.”表示水。
输出格式
为每一个段落输出一行解。如果船的位置放得正确(即棋盘上只存在相互之间不能接触的方形,如果两个“#”号上下相邻或左右相邻却分属两艘不同的船只,则称这两艘船相互接触了)。就输出一段话“There are S ships.”,S表示船只的数量。否则输出“Bad placement.”。
输入
6 8
.....#.#
##.....#
##.....#
.......#
#......#
#..#...#
输出
There are 5 ships.
#include<iostream>
#include<cstring>
using namespace std;
const int N = 1010;
int n, m;
char g[N][N];
int cnt, R, C;
int x1, y1, x2, y2;
// 函数功能:判断(x1, y1),(x2, y2)区域中是否为矩形
bool Rect()
{
for(int i = x1; i < x2; i++)
for(int j = y1; j < y2; j++)
if(g[i][j] == '.') return false;
return true;
}
// 函数功能:搜索每一个#所在的连通块的左上与右下坐标
void dfs(int x, int y)
{
g[x][y] = '*';
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
for(int i = 0; i < 4; i++)
{
int a = x + dx[i], b = y + dy[i];
if(a >= 0 && a < R && b >= 0 && b < C && g[a][b] == '#')
{
x1 = min(x1, a);
y1 = min(y1, b);
x2 = max(x2, a);
y2 = max(y2, b);
dfs(a, b);
}
}
}
int main()
{
cin >> R >> C;
for(int i = 0; i < R; i++)
for(int j = 0; j < C; j++)
cin >> g[i][j];
for(int i = 0; i < R; i++)
for(int j = 0; j < C; j++)
{
if(g[i][j] == '#')
{
x1 = x2 = i;
y1 = y2 = j;
dfs(i, j);
if(!Rect())
{
printf("Bad placement.");
return 0;
}
else cnt ++;
}
}
printf("There are %d ships.", cnt);
return 0;
}
解题思路:扫描矩阵的每一个元素,如果是#则搜索它所在的连通块的区域,并判断是否为矩形。因为每次搜索到#都会标记为*,所以在搜索过程中会剪支。