练习题
百鸡问题
题目链接
题目大意:x+y+z=10, 5x + 3y + 1/3*z = n.求解所有满足的x,y,z.
- 直接暴力搜索.
- 为了避免小数,两边同时乘三
代码:
#include<iostream>
using namespace std;
int main(){
int n,x,y,z;
while(cin >> n){
for(int i=0; i<=100; i++){
for(int j=0; j<=100; j++){
if(i * 15 + 9 * j + (100-i-j) <= 3 * n){
cout << "x="<<i<<",y="<<j<<",z="<<100-i-j<< endl;
}
}
}
}
return 0;
}
abc
题目链接
a、b、c均是0到9之间的数字,abc、bcc是两个三位数,且有:abc+bcc=532。求满足条件的所有a、b、c的值。
- 直接爆搜
代码:
#include <iostream>
using namespace std;
int main(){
for(int a=0; a<=9; a++){
for(int b=0; b<=9;b++){
for(int c=0;c<=9;c++){
if(a*100 + b*10+c+b*100+c*10+c==532)
cout << a << " " << b << " " << c << endl;
}
}
}
}
Old Bill
题目链接
给一个N, 给出axyzb中的x,y,z,求a,b的值(其中a不为0)使得axyzb%N=0,若有多个解,给出最大的。
- 直接爆搜
代码:
#include <iostream>
using namespace std;
int main(){
int N,x,y,z,a,b;
int found = 0;
while(cin >> N){
cin >> x >> y >> z;
found = 0;
for(a = 9; a>=1; a--){
for(b=9; b>=1; b--){
if((a * 10000+x*1000+y*100+z*10+b)%N==0){
cout << a << " " << b << " " << (a * 10000+x*1000+y*100+z*10+b)/N << endl;
found = 1;
break;
}
}
if(found)
break;
}
if(!found)
cout << 0 << endl;
}
return 0;
}
胜利大逃亡
题目链接
题目大意:典型的使用bfs进行迷宫路径搜索问题.
- 一个grad存地图、一个flag保存是否已经到过该地方、一个队列保存待扩展状态节点。
- 注意Q.pop()的位置.
代码:
#include<stdio.h>
#include<queue>
#include<cstring>
using namespace std;
struct Unit{
int x,y,z,t;
};
const int max_n = 55;
int grad[max_n][max_n][max_n];
int flag[max_n][max_n][max_n];
queue<Unit> Q;
void init(){
memset(flag,0,sizeof(flag));
while(!Q.empty())
Q.pop();
}
int m[6][3] = {{1,0,0},{-1,0,0},{0,1,0},{0,-1,0},{0,0,1},{0,0,-1}};
int main(){
int T,A,B,C,t,ans;
scanf("%d", &T);
while(T--){
init();
scanf("%d%d%d%d", &A, &B, &C, &t);
for(int i=0; i<A; i++){
for(int j=0; j<B; j++){
for(int k=0; k<C; k++)
scanf("%d",&grad[i][j][k]);
}
}
Unit tmp;
tmp.x= tmp.y = tmp.z = tmp.t = 0;
flag[0][0][0] = 1;
Q.push(tmp);
while(!Q.empty()){
Unit top = Q.front();
if(top.x == A-1 && top.y == B-1 && top.z == C-1){
ans = top.t;
break;
}
// 这里Q.pop()一定要写在后面.
Q.pop();
int nx, ny, nz;
for(int d=0; d<6;d++){
nx = top.x + m[d][0];
ny = top.y + m[d][1];
nz = top.z + m[d][2];
if(nx>=0&&nx<A&&ny>=0&&ny<B&&nz>=0&&nz<C&&flag[nx][ny][nz]==0&&grad[nx][ny][nz]==0){
Unit tmp;
tmp.x = nx;
tmp.y = ny;
tmp.z = nz;
tmp.t = top.t+1;
flag[nx][ny][nz] = 1;
Q.push(tmp);
}
}
}
if(Q.empty() || ans > t)
printf("-1\n");
else{
printf("%d\n", ans);
}
}
return 0;
}
非常可乐
题目链接
题目大意:三个杯子,相互倾倒其中的水,使两个杯子中的水一样多,问是否可行,可行输出最少次数。
- 抽象为搜索问题。用一个三维数组表示三个杯子中的水
- 抽象倒的动作为一个函数。
- 一个状态节点可以扩展6个节点。
代码如下:
#include <iostream>
#include <queue>
#include <cstring>
using namespace std;
const int max_n = 105;
int flag[max_n][max_n][max_n];
int S,N,M;
struct Unit{
int s,n,m,t;
};
queue<Unit> Q;
void init(){
while(!Q.empty())
Q.pop();
memset(flag,0,sizeof(flag));
}
void trans(int &x, int &y, int y_c){
int left_c = y_c - y;
if(left_c >= x){
y = y + x;
x = 0;
return;
}
y = y_c;
x = x - left_c;
}
int main(){
int ans;
while(cin >> S >> N >> M){
if(S == 0 && N == 0 && M == 0)
break;
init();
if(S % 2){
cout << "NO" << endl;
continue;
}
Unit tmp;
tmp.s = S;
tmp.n = tmp.m = tmp.t = 0;
flag[tmp.s][tmp.n][tmp.t] = 1;
Q.push(tmp);
while(!Q.empty()){
Unit top = Q.front();
// cout << top.s << " " << top.n << " " << top.m << endl;
if(top.s * 2 == S && (top.m == 0 || top.n == 0) || top.m * 2 == S && (top.s == 0 || top.n == 0) || top.n * 2 == S && (top.s == 0 || top.n == 0)){
ans = top.t;
break;
}
Q.pop();
int ns, nn, nm;
// 从s倒入n
ns = top.s;
nn = top.n;
nm = top.m;
trans(ns, nn, N);
if(!flag[ns][nn][nm]){
Unit tmp;
tmp.s = ns;
tmp.n = nn;
tmp.m = nm;
tmp.t = top.t + 1;
flag[ns][nn][nm] = 1;
Q.push(tmp);
}
// s 倒入 m
ns = top.s;
nn = top.n;
nm = top.m;
trans(ns, nm, M);
if(!flag[ns][nn][nm]){
Unit tmp;
tmp.s = ns;
tmp.n = nn;
tmp.m = nm;
tmp.t = top.t + 1;
flag[ns][nn][nm] = 1;
Q.push(tmp);
}
// n 倒入 m
ns = top.s;
nn = top.n;
nm = top.m;
trans(nn, nm, M);
if(!flag[ns][nn][nm]){
Unit tmp;
tmp.s = ns;
tmp.n = nn;
tmp.m = nm;
tmp.t = top.t + 1;
flag[ns][nn][nm] = 1;
Q.push(tmp);
}
// n 倒入 s
ns = top.s;
nn = top.n;
nm = top.m;
trans(nn, ns, S);
if(!flag[ns][nn][nm]){
Unit tmp;
tmp.s = ns;
tmp.n = nn;
tmp.m = nm;
tmp.t = top.t + 1;
flag[ns][nn][nm] = 1;
Q.push(tmp);
}
// m 倒入 n
ns = top.s;
nn = top.n;
nm = top.m;
trans(nm, nn, N);
if(!flag[ns][nn][nm]){
Unit tmp;
tmp.s = ns;
tmp.n = nn;
tmp.m = nm;
tmp.t = top.t + 1;
flag[ns][nn][nm] = 1;
Q.push(tmp);
}
// m 倒入 s
ns = top.s;
nn = top.n;
nm = top.m;
trans(nm, ns, S);
if(!flag[ns][nn][nm]){
Unit tmp;
tmp.s = ns;
tmp.n = nn;
tmp.m = nm;
tmp.t = top.t + 1;
flag[ns][nn][nm] = 1;
Q.push(tmp);
}
}
if(Q.empty())
cout << "NO" << endl;
else
cout << ans << endl;
}
return 0;
}
汉诺塔三
题目链接
题目大意:汉诺塔问需要移动几次。两个限制:1、大盘不能放小盘下。2、必须往相邻的移动。
- 个人感觉比较像动态规划。
- 定义dp[i],i个盘需要次数。目标dp[n]
- 根具推演,状态转移dp[i] = 3 * dp[i-1] + 2
- 初态: dp[1] = 2.
代码:
#include <iostream>
using namespace std;
const int max_n = 36;
typedef long long LL;
LL dp[max_n];
int main(){
dp[1] = 2;
for(int i=2; i<max_n; i++)
dp[i] = 3 * dp[i-1] + 2;
int n;
while(cin >> n){
cout << dp[n] << endl;
}
return 0;
}
Prime ring problem.
题目链接
题目大意:有n个数,组成一圈,要求相邻两个数相加为质数。按字典序输出顺时针的所有可能.
- 使用dfs, 注意使用dfs时脑海中一定要有搜索树的样子。
代码:
#include <iostream>
#include <cstring>
using namespace std;
const int max_n = 21;
int flag[max_n];
int ans[max_n];
void init(){
memset(flag, 0, sizeof(flag));
}
int is_prime(int i){
if(i == 0 || i == 1) return 0;
for(int j=2; j*j<=i; j++){
if(i % j == 0)
return 0;
}
return 1;
}
int n;
void dfs(int i){ //寻找第i个数,从0开始
if(i == n){ // 找完了,但是还需判断和第一个是否为质数
if(is_prime(ans[n-1] + ans[0])){
for(int t=0; t<n; t++){
if(t) cout << " " << ans[t];
else cout << ans[t];
}
cout << endl;
}
}
for(int j=1; j<=n; j++){
if(flag[j]==0 && is_prime(j + ans[i-1])){
flag[j] = 1;
ans[i] = j;
dfs(i+1);
flag[j] = 0;
}
}
}
int main(){
int cnt = 1;
while(cin >> n){
init();
ans[0] = 1;
flag[1] =1;
cout << "Case " << cnt++ << ":" << endl;
dfs(1);
cout << endl;
}
return 0;
}
Oil Deposits
题目链接
题目大意: 一个矩阵,两种符号,问其中一种符号的块数。若其和周围八个方向的连在一起为一块。
- 个人认为这类2维地图类的搜索问题不必抽象搜索树。
- 一个flag记录是否搜索过该位置,一个grad记录地图。从八个方向扩展节点。具体条件间代码.
代码:
#include<iostream>
#include<cstring>
using namespace std;
const int max_n = 105;
int flag[max_n][max_n];
char grad[max_n][max_n];
void init(){
memset(flag, 0, sizeof(flag));
}
int m, n;
int dirction[8][2] = {{0,1},{1,0},{-1,0},{0,-1},{1,1},{1,-1},{-1,1},{-1,-1}};
void dfs(int x, int y){
flag[x][y] = 1;
for(int i=0; i<8; i++){
int nx = x + dirction[i][0];
int ny = y + dirction[i][1];
if(nx < 0 || nx >= m) continue;
if(ny < 0 || ny >= n) continue;
if(grad[nx][ny] == '*') continue;
if(flag[nx][ny]) continue;
dfs(nx, ny);
}
}
int main(){
while(cin >> m >> n){
if(m == 0 ) break;
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
cin >> grad[i][j];
}
}
init();
int ans = 0;
for(int i=0; i<m; i++){
for(int j=0; j<n; j++){
if(grad[i][j] == '*') continue;
if(flag[i][j]) continue;
// cout << i << " " <<j << endl;
ans++;
dfs(i, j);
}
}
cout << ans << endl;
}
return 0;
}
全排列
题目大意:输出字符串各个字符的全排列,按字典序。
- 解法一:直接STL。
代码:
#include<iostream>
#include<algorithm> //映入此库
using namespace std;
int main(){
string s;
while(cin>>s){
sort(s.begin(),s.end());
do{
cout<<s<<endl;
}while(next_permutation(s.begin(),s.end()));
cout<<endl;
}
return 0;
}
解法二:手动实现全排列
- 定义dfs(int i):找寻第i个位置的答案。且字符串从i到最后满足字典序。由于使用了交换策略,因此不需要使用flag标记是否使用过
- 入口dfs(0), 出口dfs(n)。
- 出口条件 i==n. 并输出
- 因为要满足字典序,因此中途不能直接交换.
代码:
#include <stdio.h>
#include <stdlib.h>
#include <cstring>
#include <algorithm>
using namespace std;
const int max_n = 7;
char str[max_n];
int len;
void dfs(int i){
if(i == len)
printf("%s\n", str);
char tmp;
for(int j=i; j<len; j++){
tmp = str[j];
for(int k=j; k>=i+1; k--){
str[k] = str[k-1];
}
str[i] = tmp;
dfs(i+1);
tmp = str[i];
for(int k=i+1;k<=j;k++)
str[k-1] = str[k];
str[j] = tmp;
}
}
int main(){
while(scanf("%s", str)!=EOF){
len = strlen(str);
sort(str, str+len);
dfs(0);
printf("\n");
}
return 0;
}
Temple of the bone
题目链接
题目大意:迷宫逃亡问题,问能否刚好到达, 到达过的地方不能再到达。
- 注意是刚好到达。因此不能用bfs,而应该采取dfs,搜索每一种路径的存在。
- 定义dfs(int x, int y, int t)为在时间t是在x,y位置探寻。
- 出口条件:1: (x,y)是终点但是t不是指定t。 2: t 大于指定t。
- 注意当找到路径之后,便不要再扩展其他节点了。因此代码中有一句 if(found) return.
代码:
#include <iostream>
#include <cstring>
using namespace std;
const int max_n = 8;
int flag[max_n][max_n];
char grad[max_n][max_n];
int found;
struct Unit{
int x,y,t;
};
void init(){
memset(flag, 0, sizeof(flag));
found = 0;
}
int dirction[4][2] = {{0,1}, {1,0},{-1,0},{0,-1}};
int M,N,T;
Unit S, D;
void dfs(int x, int y, int t){
flag[x][y] = 1;
if(t > T)
return;
if(x == D.x && y == D.y){
if(t == T){
found = 1;
return;
}
else
return;
}
for(int i=0; i<4; i++){
int nx = x + dirction[i][0];
int ny = y + dirction[i][1];
if(nx < 0 || nx >= M) continue;
if(ny < 0 || ny >= N) continue;
if(flag[nx][ny]) continue;
if(grad[nx][ny] == 'X') continue;
flag[nx][ny] = 1;
dfs(nx, ny, t+1);
if(found) return;
flag[nx][ny] = 0;
}
}
int main(){
while(cin >> M >> N >> T){
if(M == 0) break;
init();
for(int i=0; i<M; i++){
for(int j=0; j<N; j++){
cin >> grad[i][j];
if(grad[i][j] == 'S'){
S.x = i;
S.y = j;
S.t = 0;
}
if(grad[i][j] == 'D'){
D.x = i;
D.y = j;
}
}
}
dfs(S.x, S.y, S.t);
if(found)
cout << "YES" << endl;
else
cout << "NO" << endl;
}
}
总结
对本章来说。基本上是三种题型,一种是爆搜,一种是广搜,一种是深搜。主要注意。
-
暴力搜索
- 略
-
广搜
- 使用队列存储待扩展状态节点。
- 注意Q.pop()的位置在break后面.
-
深搜
- 主要借助递归实现。
- 深搜时脑海中一定要有搜索树的图像,其实广搜也要有,但是个人感觉深搜更重要。
其他知识
- next_permutation()的用法
- 素数的判断。