专题五 搜索
102400226 石华波
Vjudge账号:DustLi
问题一 自然数的拆分问题
问题陈述
任何一个大于 1 的自然数 n,总可以拆分成若干个小于 n 的自然数之和。现在给你一个自然数 n,要求你求出 n 的拆分成一些数字的和。每个拆分后的序列中的数字从小到大排序。然后你需要输出这些序列,其中字典序小的序列需要优先输出。
解题思路
DFS。
AC代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
#include<iostream>
using namespace std;
using ll = long long;
int Pr[11]={1};
int N;
void Print(int M) {
for(int i=1;i<M;i++) cout<<Pr[i]<<'+';
if(M!=1) cout<<Pr[M]<<endl;
}
void DFS(int T) {
for(int i=Pr[T-1];i<=N;i++) {
Pr[T]=i;
N-=i;
if(N==0) Print(T);
else DFS(T+1);
N+=i;
}
}
int main() {
cin>>N;
DFS(1);
return 0;
}
|
问题二 填涂颜色
问题陈述
由数字 0 组成的方阵中,有一任意形状的由数字 1 构成的闭合圈。现要求把闭合圈内的所有空间都填写成 2。例如:6×6 的方阵(n=6),涂色前和涂色后的方阵如下:
如果从某个 0 出发,只向上下左右 4 个方向移动且仅经过其他 0 的情况下,无法到达方阵的边界,就认为这个 0 在闭合圈内。闭合圈不一定是环形的,可以是任意形状,但保证闭合圈内的 0 是连通的(两两之间可以相互到达)。
解题思路
”寻找闭合圈中的0“是一个比较难的操作,不妨转化为”寻找与边界联通的0并标记“。
另外,可以先将所有的0染为2,然后将与边界联通的2染回0。
以Dir数组存储转向数对,BFS。
AC代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
#include<iostream>
#include <queue>
using namespace std;
using ll = long long;
int Mar[31][31];
int Dir[5][5]={{0,0},{0,1},{0,-1},{-1,0},{1,0}};
queue<pair<int,int>> Pos;
int main() {
int N;
cin>>N;
for(int i=1;i<=N;i++) for(int j=1;j<=N;j++) cin>>Mar[i][j];
for(int i=0;i<=N+1;i++) for(int j=0;j<=N+1;j++) if(Mar[i][j]==0) Mar[i][j]=2;
Pos.push(make_pair(0,0));
while(!Pos.empty()) {
int x=Pos.front().first;
int y=Pos.front().second;
Pos.pop();
Mar[x][y]=0;
for(int i=1;i<=4;i++) if(Mar[x+Dir[i][0]][y+Dir[i][1]]==2) Pos.push(make_pair(x+Dir[i][0],y+Dir[i][1]));
}
for(int i=1;i<=N;i++) {
for(int j=1;j<=N;j++) {
cout<<Mar[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
|
问题三 素数密度
问题陈述
古老的显示屏是由 N×M 个像素(Pixel)点组成的。一个像素点的位置是根据所在行数和列数决定的。例如 P(2,1) 表示第 2 行第 1 列的像素点。那时候,屏幕只能显示黑与白两种颜色,人们用二进制 0 和 1 来表示。0 表示黑色,1 表示白色。当计算机发出一个指令:P(x,y)=1,则屏幕上的第 x 行第 y 列的阴极射线管就开始工作,使该像素点显示白色,若 P(x,y)=0,则对应位置的阴极射线管不工作,像素点保持黑色。在某一单位时刻,计算机以 N×M 二维 01 矩阵的方式发出显示整个屏幕图像的命令。
例如,屏幕是由 3×4 的像素点组成,在某单位时刻,计算机发出如下命令:
000001011110
对应屏幕显示应为:

假设放大后,一个格子表示一个像素点。
由于未知的原因,显示黑色的像素点总是受显示白色的像素点的影响——可能是阴极射线管工作的作用。并且,距离越近,影响越大。这里的距离定义如下:
设有像素点 P1(x1,y1) 和像素点 P2(x2,y2),则它们之间的距离 D(P1,P2)=∣x1−x2∣+∣y1−y2∣。
在某一时刻,计算机发出显示命令后,科学家们期望知道,每个像素点和其最近的显示白色的像素点之间的最短距离是多少——科学家们保证屏幕上至少有一个显示白色的像素点。
上面的例子中,像素 P(1,1) 与最近的白色像素点之间的距离为 3,而像素 P(3,2) 本身显示白色,所以最短距离为 0。
解题思路
BFS,对每个黑块处理来找到距离最近的白块操作次数太多,易TLE,不妨从白块出发,依次找到距离最近的白块的距离为1、2…的黑块。注意检查坐标是否合法。
AC代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
#include<iostream>
#include <queue>
#include <string.h>
using namespace std;
using ll = long long;
int N,M;
int Mar[190][190];
int Dis[190][190];
bool Vis[190][190];
int Dir[5][5]={{0,0},{0,1},{0,-1},{-1,0},{1,0}};
queue<pair<int,int> > Pos;
bool Ava(int x,int y) {
return x>=1&&x<=N&&y>=1&&y<=M;
}
int main() {
cin>>N>>M;
char c;
for(int i=1;i<=N;i++) {
for(int j=1;j<=M;j++) {
cin>>c;
Mar[i][j]=c-'0';
if(Mar[i][j]==1) {
Dis[i][j]=0;
Vis[i][j]=true;
Pos.push(make_pair(i,j));
}
}
}
while(!Pos.empty()) {
int x=Pos.front().first;
int y=Pos.front().second;
Pos.pop();
for(int i=1;i<=4;i++) {
int xi=x+Dir[i][0];
int yi=y+Dir[i][1];
if(Ava(xi,yi)&&!Vis[xi][yi]) {
Pos.push(make_pair(xi,yi));
Vis[xi][yi]=true;
Dis[xi][yi]=Dis[x][y]+1;
}
}
}
for(int i=1;i<=N;i++) {
for(int j=1;j<=M;j++) cout<<Dis[i][j]<<" ";
cout<<endl;
}
return 0;
}
|
问题4 健康的荷斯坦奶牛
问题陈述
农民 John 以拥有世界上最健康的奶牛为傲。他知道每种饲料中所包含的牛所需的最低的维他命量是多少。请你帮助农夫喂养他的牛,以保持它们的健康,使喂给牛的饲料的种数最少。
给出牛所需的最低的维他命量,输出喂给牛需要哪些种类的饲料,且所需的饲料剂量最少。
维他命量以整数表示,每种饲料最多只能对牛使用一次,数据保证存在解。
解题思路
DFS,数目未知选用Vector容器。
其中:
if (!Ans.empty() && Cho.size() >= Ans.size()) return;
用于剪枝,防止数据量过大超时。
AC代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
#include<iostream>
#include <queue>
using namespace std;
using ll = long long;
int V,G;
int VatN[26];
int VatS[16][26];
vector<int> Cho;
vector<int> Ans;
bool Check() {
for(int i=1;i<=V;i++) {
int Sum=0;
for(auto j:Cho) Sum+=VatS[j][i];
if(Sum<VatN[i]) return false;
}
return true;
}
void DFS(int x) {
if (!Ans.empty() && Cho.size() >= Ans.size()) return;
if(x>G) {
if(Check()&&(Ans.empty()||Cho.size()<Ans.size())) Ans=Cho;
return;
}
Cho.push_back(x);
DFS(x+1);
Cho.pop_back();
DFS(x+1);
}
int main() {
cin>>V;
for(int i=1;i<=V;i++) cin>>VatN[i];
cin>>G;
for(int i=1;i<=G;i++) for(int j=1;j<=V;j++) cin>>VatS[i][j];
DFS(1);
cout<<Ans.size()<<' ';
for(auto i:Ans) cout<<i<<' ';
return 0;
}
|
问题五 GRZ-Ridges and Valleys
问题陈述
给定一个 n×n 的网格状地图,每个方格 (i,j) 有一个高度 w**ij。如果两个方格有公共顶点,则它们是相邻的。
定义山峰和山谷如下:
- 均由地图上的一个连通块组成;
- 所有方格高度都相同;
- 周围的方格(即不属于山峰或山谷但与山峰或山谷相邻的格子)高度均大于山谷的高度,或小于山峰的高度。
求地图内山峰和山谷的数量。特别地,如果整个地图方格的高度均相同,则整个地图既是一个山谷,也是一个山峰。
解题思路
BFS,八联通,注意Dir中应有八个方向。
处理BFS时,将与目前元素高度相等的元素加入队列,并标记已访问,对于与当前元素高度不同的元素,若当前元素较大,则当前联通块不可能为山谷,反之,则当前联通块不可能为山峰。最后计数输出。
AC代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
#include<iostream>
#include <queue>
using namespace std;
using ll = long long;
int N;
int CntP,CntV;
int Dir[9][9]={{0,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1},{-1,-1},{-1,0}};
int Mar[1001][1001];
bool Vis[1001][1001];
queue<pair<int, int>> Pos;
bool Ava(int i,int j) {
return i>=1&&j>=1&&i<=N&&j<=N;
}
void BFS() {
bool isPeak=true;
bool isValley=true;
while(!Pos.empty()) {
int x=Pos.front().first;
int y=Pos.front().second;
Pos.pop();
for(int i=1;i<=8;i++) {
int xi=x+Dir[i][0];
int yi=y+Dir[i][1];
if(Ava(xi,yi)) {
if(Mar[xi][yi]==Mar[x][y]&&!Vis[xi][yi]) {
Pos.push(make_pair(xi,yi));
Vis[xi][yi]=true;
}
if(Mar[xi][yi]>Mar[x][y]) isPeak=false;
if(Mar[xi][yi]<Mar[x][y]) isValley=false;
}
}
}
if(isPeak) CntP++;
if(isValley) CntV++;
}
int main() {
cin>>N;
for(int i=1;i<=N;i++) for(int j=1;j<=N;j++) cin>>Mar[i][j];
for(int i=1;i<=N;i++) {
for(int j=1;j<=N;j++) {
if(!Vis[i][j]) {
Pos.push(make_pair(i,j));
BFS();
}
}
}
cout<<CntP<<" "<<CntV<<endl;
return 0;
}
|
问题六 DFS
问题陈述
-
一个如下的 6×6 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。

上面的布局可以用序列 2 4 6 1 3 5 来描述,第 i 个数字表示在第 i 行的相应位置有一个棋子,如下:
行号 1 2 3 4 5 6
列号 2 4 6 1 3 5
这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。
并把它们以上面的序列方法输出,解按字典顺序排列。
请输出前 3 个解。最后一行是解的总个数。
解题思路
DFS,同一条主对角线上的元素横纵坐标之和相等,同一条副对角线上的元素横纵坐标之差相等,故两个一维数组即可存储对角线的占用状态,再开两个一维数组用于存储列的占用状态和当前方案。
Cnt<=3时输出本次方案,最后输出总方案数。
AC代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
#include<iostream>
#include <queue>
using namespace std;
using ll = long long;
int N;
int Cnt;
bool MDia[27];
bool SDia[27];
bool Col[27];
int Ans[14];
void DFS(int x) {
if(x>N) {
Cnt++;
if(Cnt<=3) {
for(int i=1;i<=N;i++) cout<<Ans[i]<<" ";
cout<<endl;
}
return;
}
for(int i=1;i<=N;i++) {
if(!(Col[i]||MDia[x+i]||SDia[x-i+N])) {
Ans[x]=i;
Col[i]=true;
MDia[x+i]=true;
SDia[x-i+N]=true;
DFS(x+1);
Col[i]=false;
MDia[x+i]=false;
SDia[x-i+N]=false;
}
}
}
int main() {
cin>>N;
DFS(1);
cout<<Cnt<<endl;
return 0;
}
|
学习总结
本专题内容主要为搜索,通过学习,提高了自己的能力。