八数码难题
参考代码:(刘汝佳《算法竞赛入门经典》,源代码在首页置顶区的代码仓库
注意学习判重的实现
编码和解码
//无权图上的最短路,可用BFS求解
#include<bits/stdc++.h>
using namespace std;
const int MAX = 1000000;
typedef int State[9] ; // 定义“状态”类型
State st[MAX],goal;//状态数组
int dis[MAX];//距离数组
//ps: 如果需要答应路径, 可用在这加一个 father【MAX】 ,自己实现下吧
const int dx[] = {-1, 1, 0, 0};
const int dy[] = {0, 0, 1, -1};
int vis[MAX], fact[9];
void init_() {
fact[0] = 1;
for(int i = 1; i < 9; i++) fact[i] = fact[i-1] * i;
};
int try_to_insert(int rear) {
int code = 0; // 将st[rear] 映射到整数code
// ps: 这里直接用st[rear]没有错, 因为bfs中用的是“引用”, st[rear]已更新
for (int i = 0; i<9; i++)
{
int cnt = 0;
for (int j = i + 1; j<9; j++)
if (st[rear][j]<st[rear][i])cnt++;
code += fact[8 - i] * cnt;
}
if (vis[code])return 0;
return vis[code] = 1;
}
int bfs() {//bfs: 返回目标状态在st[]中的下标 (即goal在dis[] 中的下标
init_();//初始化查找表
int front = 1, rear = 2; // 0 表示不存在, 找到的话返回front即可
while(front < rear) {
State& s = st[front];//“引用” -- 换名字
if(memcmp(goal, s, sizeof(s)) == 0) return front;//找到了目标位置,成功返回
/*没有找到goal就继续扩展,而扩展得到的rear就可能是答案,
然后接下来继续while经memcpy找到答案时,返回的front即为上一次的rear, 对应的距离也就是上一次的dis[rear]了
所以, binggo ! */
int z;
for(z = 0; z < 9; z++) if(!s[z] ) break;//找“0” 的位置
int x = z / 3, y = z % 3; //获取0的行列编号
for(int d = 0; d < 4; d++) {
int nowx = x + dx[d], nowy = y + dy[d], nowz = nowx*3 + nowy;//获取新0的位置
if(nowx>=0 && nowy>=0 && nowx<3 && nowy<3) {
State &t = st[rear]; // 再次“换名字”, 用以修改队尾元素
memcpy(&t, &s, sizeof(s) ); //扩展新的结点
t[nowz] = 0;
t[z] = s[nowz];//移动
dis[rear] = dis[front] + 1; //更新距离值
if(try_to_insert(rear) ) rear++; // 如果成功插入查找表, 修改队尾指针 为什么要用 try_to_insert() ?
//用于判重, 防止重复扩充
}
}
front++; //继续while
}
return 0;// 没有找到goal, 返回0
}
int main() {
for(int i = 0; i < 9; i++) scanf("%d", &st[1][i]);
for(int i = 0; i < 9; i++) scanf("%d", &goal[i]);
int ans = bfs();
if(ans == 0) printf("-1");//没找到
else printf("%d",dis[ans]);
return 0;
}
/*
2 8 3 1 0 4 7 6 5
1 2 3 8 0 4 7 6 5
ans = 4
*/
hash 技术
(ps; 这的2,3 仅在init_ () 和try_to_insert() 上做修改 防止视觉疲劳, 引起不适
// hash在竞赛中用的好广泛的哦
const int hashsize = 1000003;
int head[hashsize], next[hashsize];//往往有不同节点的哈希值相等, 这时把哈希值相同的状态组织成链表
void init_() {
memset(head, 0, sizeof(head));
};
int hash(State s) {
int v = 0;
for(int i = 0; i < 9; i++) v = v*10 + s[i];//将9个数字合成为九位数
return v % hashsize;// 确保hash 函数值是不超过 hash表 大小的非负整数
}
int try_to_insert(int rear) {
int h = hash(st[rear]);
int i = head[h];
while(i) { // 从表头开始查找链表
if(memcpy(st[i], st[rear], sizeof(st[rear])) == 0) return 0;// 找到了, 插入失败
i = next[i];
}// 本人在luogu 上测评时发现这的while 改成for 会RE掉...有人可以告诉我为啥吗, 还是建议你们写while吧
next[i] = head[h];
head[h] = rear;//
return 1;
}
STL大法好
STL 可作为跳板
---刘汝佳
set <int> vis;
void init_() { vis.clear() ; }
int try_to_insert(int rear) {
int v = 0;
for(int i = 0; i < 9; ++i) v = v*10 + st[rear][i];
if(vis.count(v) ) return 0; // vis.count() 若在vis里面, 返回1
vis.insert(v);
return 1;
}
倒水问题
描述:
题目描述
有三个容量分别为a,b,c升的容器(a,b,c都是正整数,且都不超过200),刚开始的时候第一个和第二个杯子都是空的,只有第三个杯子装满了c升水。允许从一个容器把水倒入另一个容器中,直到一个容器空了或者是另一个容器满了,允许无限次的进行这样的倒水操作。
你的任务是编写一个程序来计算出最少需要倒多少升水才能让其中某一个杯子中的水有d升(d是不超过200的正整数)?如果无法做到恰好是d升,就让某一个杯子里的水是d‘升,其中d'<d并且尽量接近d。如果能够找到这样的d',你还是需要计算出其中某一个杯子达到d'升时,最少需要倒多少升水。
输入输出格式
输入格式:
输入的第一行是一个整数T,表示测试数据组数。 接下来T行,每行4个用空格隔开的整数分别表示a,b,c,d。
输出格式:
对于每组测试数据,输出一行,包含两个整数,第一个整数表示最少的倒水总量,第二个整数表示目标倒水量(d或者d')。
输入输出样例
输入样例#1:
2
2 3 4 2
96 97 199 62
输出样例#1:
2 2
9859 62
代码(有问题
#include<cstdio>
#include<string.h>
#include<queue>
using namespace std;
#define MAX 200+99
int T;
struct node{
int v[3], dis;//注:这是最少倒水量,所以用堆
bool operator < (const node& rhs) const {
return dis > rhs.dis ;
}
};
int vis[MAX][MAX], cap[3], goal,ans[MAX];//ans[i]表示到成i升的水的最小倒水量
void update(node x) {
for(int i = 0; i <= 2; i++) {
if(ans[i] < 0 || ans[i] > x.dis ) {
ans[i] = x.dis ;
}
}
return ;
}
void solve() {
memset(vis, 0, sizeof(vis));
memset(ans, -1, sizeof(ans));//有可能ans为0
priority_queue<node> q;//在里面定义好
node u,e,start;
start.dis = 0,start.v[0] = start.v[1] = 0, start.v[2] = cap[2];
q.push(start);
while(!q.empty() ) {
u = q.top() ; q.pop() ;
update(u);
if(ans[goal] >= 0) break;
//if(vis[u.v[0]][u.v[1]]]) continue ;// 试试
for(int i = 0; i <= 2; i++) //i往j里加
for(int j = 0; j <= 2; j++) if(j != i) {
if(u.v[i] == 0 || u.v[j] == cap[j]) continue;
int tmp = min(cap[j]-u.v[j], u.v[i]);
memcpy(&e, &u, sizeof(u));
e.dis = u.dis + tmp;
e.v[i] -= tmp, e.v[j] += tmp;
if(!vis[e.v[0]][e.v[1] ]) {
q.push(e);
vis[e.v[0]][e.v[1] ] = 1;
}
}
}
while(goal >= 0) {
if(ans[goal] >= 0) {
printf("%d %d
", ans[goal], goal);
return ;
}
goal--;
}
}
int main() {
scanf("%d",&T);
while(T--) {
scanf("%d%d%d%d",&cap[0],&cap[1],&cap[2],&goal);
solve();
}
return 0;
}
A*
介绍
一种最基础的启发式搜索,说人话:帮助我们从"瞎搜"转换到“有目的的搜索”
核心: 设置一个估值函数,使估算值与实际值更接近,从而实现搜索速度加快。每次扩展节点时选择估价最小的节点进行扩展。如果一个节点与待扩展的节点重复,并且这个节点的估值函数更小,则用这个节点代替原待扩展节点。
估计
f(n) = g(n) + h(n)
f(n)为由初始状态(以下简称起点),经过状态n(以下简称n点),到达目标节点(以下简称终点)的估计代价,g(n)是在状态空间中由起点到n点的实际代价,h(n)是由n点到终点的最佳路径的估计代价。(对于路径搜索,状态就是图上的节点,代价就是距离,这就是为啥我简称)
保证找到最短路径(最优解),关键在于f(n) (或者说是h(n) ) 的选取, 设d(n) 为n到终点的实际距离
- 如果h(n) < d(n), 则搜索到的节点多,范围大,效率低,但能找到最优解
- 如果h(n) == d(n), 则搜索将按照最优解的路径,效率最高
- 如果h(n) > d(n), 则搜索到的节点会少,但不能保证找到最优解
所以我们希望有个“乐观”的估计,即h(n) <= d(n)
伪代码
把起始格添加到"开启列表"
do {
寻找"开启列表"中f()最小的,叫做当前格
把它放入"关闭列表",对当前格相邻8格中的每一个 {
if(它不可以通过 || 已在"关闭列表") 什么都不做,break;
if(不在"开启列表") {
以当前格为父节点,算出这个节点的FGH值
//H为忽略障碍后到终点的距离
把这个节点加入"开启列表"
}
if(已在"开启列表中" && 用G值判断这个新的路径是否更好,G小好些) {
把这一格的父节点设为当前格,并重新计算这一格GF值
}
}
}while(目标格在"开启列表"中,此时路径已被找到)
//若"开启列表"为空,则无解
最后从目标节点开始,沿着父亲节点走,直到回到起始格,得出路径