一、暴力求解
特别注意时间复杂度(枚举情况个数)
#define _CRT_SECURE_NO_DEPRECATE
#include<stdio.h>
int main()
{
int n;
while (scanf("%d", &n) != EOF)
{
for (int i = 0; i <= 100; i++)
{
for (int j = 0; j <= 100; j++)
{
int z = 100 - i - j;
//避免除法带来的精度损失
if (15 * i + 9 * j + z <= 3*n)
{
printf("x=%d,y=%d,z=%d\n", i, j, z);
}
}
}
}
return 0;
}
为避免除法带来的精度损是,这里采用对不等式两端的所有数字都乘以3,这也是避免除法的 常用技巧
题目二:
设a、b、c均是0到9之间的数字,abc、bcc是两个三位数,且有:abc+bcc=532。求满足条件的所有a、b、c的值。
#include <stdio.h>
int main()
{
int N;
bool flag;
while (scanf("%d", &N) != EOF)
{
flag = false;
int X, Y, Z;
scanf("%d%d%d", &X, &Y, &Z);
int tmp = X * 1000 + Y * 100 + Z * 10;
for (int i = 9; i > 0; i--)
{
for (int j = 9; j >= 0; j--)
{
int num = i * 10000 + tmp + j;
if (num%N == 0)
{
flag = true;
printf("%d %d %d\n", i,j,num / N);
break;
}
}
if (flag)
break; //跳出全部循环
}
if (!flag)
printf("0\n");
}
return 0;
}
这里的两个点: 有了最佳答案后,要跳出两层循环,每个输入案例的标志量要初始化
二、广度优先搜索
为了能够考虑搜索的问题,我们常在搜索问题中指明某种状态,从而使搜索问题变为对状态的搜索 采取相应措施来制约状态的无限扩展
第一次查找到包含结点(x,y,z)的状态,其后查找到的任意包含该坐标的状态都不必被扩展,因为其所耗时肯定大于先被查找的状态
为了防止对无效状态的搜索,用一个标记数组,当下次再由某状态扩展出包含该坐标的状态时,则直接丢弃
#include <stdio.h>
#include<queue>
using namespace std;
bool mark[50][50][50]; //标记数组
int maze[50][50][50]; //保存立方体信息
struct N {
int x, y, z;
int t;
};
queue<N> Q; //队列中的元素为状态
int go[][3] = { //坐标变换数组
1,0,0,
-1,0,0,
0,1,0,
0,-1,0,
0,0,1,
0,0,-1
};
int BFS(int a, int b, int c) //返回其最小耗时
{
while (!Q.empty())
{
N now = Q.front();
Q.pop();
for (int i = 0; i < 6; i++) //一次扩展其相邻六个方向
{
int nx = now.x + go[i][0];
int ny = now.y + go[i][1];
int nz = now.z + go[i][2];
//若新坐标在立体方外,丢弃
if (nx < 0 || nx >= a || ny < 0 || ny >= b || nz < 0 || nz >= c)
continue;
if (maze[nx][ny][nz] == 1)
continue; //该位置是墙
if (mark[nx][ny][nz] == true)
continue; //包含该坐标的状态已经得到过了
N tmp;
tmp.x = nx;
tmp.y = ny;
tmp.z = nz;
tmp.t = now.t + 1; //新状态
Q.push(tmp);
mark[nx][ny][nz] = true;
if (nx == a - 1 && ny == b - 1 && nz == c - 1)
return tmp.t;
}
}
return -1; //所有状态都被查找,仍旧得不到
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
int a, b, c, t;
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", &maze[i][j][k]);
mark[i][j][k] = false; //初始化标记数组
}
}
}
while (!Q.empty())
Q.pop(); //清空队列
mark[0][0][0] = true; //标记起点
N tmp;
tmp.t = tmp.x = tmp.y = tmp.z = 0; //初始状态
Q.push(tmp);
int res = BFS(a, b, c);
if (res <= t)
printf("%d\n", res);
else
printf("-1\n");
}
return 0;
}
平分可乐
思路:
使用四元组(x,y,z,t)来表示一个状态,其中x,y,z分别表示三个瓶子中的可乐体积,t表示从初始状态到该状态所需的杯子间互相倾倒的次数:某一四元组经过瓶子间的相互倾倒而得到若干组新的四元组的过程
while (!Q.empty())
{
N now = Q.front();
Q.pop();
int a, b, c;
a = now.a;
b = now.b;
c = now.c;
AtoB(a, s, b, n);
if (mark[a][b][c] = false)
{
mark[a][b][c] = true;
N tmp;
tmp.a = a;
tmp.b = b;
tmp.c = c;
tmp.t = now.t + 1;
if (a == s / 2 && b == s / 2)
return tmp.t;
if (c == s / 2 && b == s / 2)
return tmp.t;
if (a == s / 2 && c == s / 2)
return tmp.t;
//否则放入队列
Q.push(tmp);
}
//重置为a,b,c没有倾倒的体积
a = now.a;
b = now.b;
c = now.c;
AtoB(b, n, a, s); //b倒向a
if (mark[a][b][c] = false)
{
mark[a][b][c] = true;
N tmp;
tmp.a = a;
tmp.b = b;
tmp.c = c;
tmp.t = now.t + 1;
if (a == s / 2 && b == s / 2)
return tmp.t;
if (c == s / 2 && b == s / 2)
return tmp.t;
if (a == s / 2 && c == s / 2)
return tmp.t;
//否则放入队列
Q.push(tmp);
}
………………………………………………………………………………
重复(a倒向c,c倒向a,b倒向c,c倒向a)
}
//初始化
N tmp;
tmp.a = s;
tmp.b = 0;
tmp.c = 0;
tmp.t = 0;
while (!Q.empty())
Q.pop();
Q.push(tmp);
mark[s][0][0] = true;
int res = BFS(s, n, m);
广度优先搜索的几个关键
- 状态:确定求解问题中的几个状态
- 状态扩展方式
- 有效状态:对有些状态我们并不对其进行再一次的扩展
- 队列:将得到的状态依次放入队尾
- 标记:判断哪些状态是
- 有效状态数:与算法时间复杂度同等数量级
- 最优:广度优先搜索常常被用来求解最优值问题
二、递归
递归的两个重要因素: 递归方式和递归出口
int F(int x)
{
if(x==0) return 1; //递归出口
else return x*F(x-1); //递归方式
}
汉诺塔变形
每次移动一定是移到中间或从中间移出
F(k) = 3*F(k-1)+2
#include<stdio.h>
long long F(int num)
{
if (num == 1)
return 2;
else
return 3 * F(num - 1) + 2;
}
int main()
{
int n;
while (scanf("%d", &n) != EOF)
{
printf("%lld\n", F(n));
}
return 0;
}
递归的应用:
①回溯法枚举(DFS)
for(int i=2;i<=n;i++)
{
if(hash[i]==false)
{
hash[i] = true;
ans[num+1] = i;
DFS(num+1);
hash[i] = false;
}
}
最后hash[i] = false; 是因为当DFS调用返回后,意味着当前num+1个数字确定为ans[1]到ans[num+1]中的值,进行新答案的搜索。所以此时ans[num+1]的值不再是已经使用过的x,而是一个新的不同于x,又未在之前使用过。对于后序数字来说,x是未被使用过的,是可以放到后序任意一个位置的
②图的遍历
三、深度优先搜索
在使用深度优先搜索时,我们更多的是求解有或者没有的问题
深度优先搜索对状态的查找采用了立即扩展得到新状态的方法,我们常使用递归函数来完成这一功能