路径寻找(隐式图遍历)

八数码难题

参考代码:(刘汝佳《算法竞赛入门经典》,源代码在首页置顶区的代码仓库

编码和解码

//无权图上的最短路,可用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\n", 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;
}

原文地址:https://www.cnblogs.com/tyner/p/11080392.html

时间: 2024-10-07 10:32:16

路径寻找(隐式图遍历)的相关文章

八数码问题+路径寻找问题+bfs(隐式图的判重操作)

Δ路径寻找问题可以归结为隐式图的遍历,它的任务是找到一条凑够初始状态到终止问题的最优路径, 而不是像回溯法那样找到一个符合某些要求的解. 八数码问题就是路径查找问题背景下的经典训练题目. 程序框架 process()  初始化vis数组,初始化初始节点到目标节点的移动距离 dfs()搜索到每一个节点,如果不是目标节点,对其依次扩展所有子节点,并判重,全部子节点搜索完全后,改变父节点:如果是目标节点成功返回 输出最少移动步数 input: 2 6 4 1 3 7 0 5 8 8 1 5 7 3 6

隐式图的遍历

好久不看都快忘了... 一些奇怪的问题可以归为隐式图的遍历 NEUOJ544 Problem Description there is an old saying,"You can not do anything without water"or"Water is the source of life". Besides, human beings are made of water. Sister Wang thinks that woman is made of

uva658(最短路径+隐式图+状态压缩)

题目连接(vj):https://vjudge.net/problem/UVA-658 题意:补丁在修正 bug 时,有时也会引入新的 bug.假定有 n(n≤20)个潜在 bug 和 m(m≤100) 个补丁,每个补丁用两个长度为 n 的字符串表示,其中字符串的每个位置表示一个 bug.第一 个串表示打补丁之前的状态("-" 表示该 bug 必须不存在,"+" 表示必须存在,0 表示无所 谓),第二个串表示打补丁之后的状态("-" 表示不存在,

hdu1818 It&#39;s not a Bug, It&#39;s a Feature!(隐式图最短路径Dijkstra)

题目链接:点击打开链接 题目描述:补丁在修bug时,有时也会引入新的bug,假设有n(n<=20)个潜在的bug和m(m<=100)个补丁,每个补丁用两个长度为n的字符串表示,其中字符串的每个位置表示一个bug.第一个串表示打补丁之前的状态('-'表示在该位置不存在bug,'+'表示该位置必须存在bug,0表示无所谓),第二个串表示打补丁之后的状态('-'表示不存在,'+'表示存在,0表示不变).每个补丁都有一个执行时间,你的任务是用最少的时间把一个所有bug都存在的软件通过打补丁的方式变得没

It&amp;#39;s not a Bug, It&amp;#39;s a Feature! (poj 1482 最短路SPFA+隐式图+位运算)

Language: Default It's not a Bug, It's a Feature! Time Limit: 5000MS   Memory Limit: 30000K Total Submissions: 1353   Accepted: 516 Description It is a curious fact that consumers buying a new software product generally do not expect the software to

【UVA】658 - It&#39;s not a Bug, it&#39;s a Feature!(隐式图 + 位运算)

这题直接隐式图 + 位运算暴力搜出来的,2.5s险过,不是正法,做完这题做的最大收获就是学会了一些位运算的处理方式. 1.将s中二进制第k位变成0的处理方式: s = s & (~(1 << pos)); 将s中二进制第k位变成1的处理方式: s = s | (1 << pos); 2.二进制运算: [1] & : 1 & 1 = 1 , 1 & 0 = 0 , 0 & 0 = 0; 快速判断奇偶性: if(a & 1);//为奇数

poj1482(隐式图求最短路)

题目链接 题意:补丁在修正bug时,有时也会引入新的bug.假定有n个潜在的bug m个补丁,每个补丁用两个长度为n的字符串表示,其中字符串的每个位置表示一个bug,第一个串表示打补丁之前的状态('-'表示该bug必须不存在,'+'表示必须存在,0表示无所谓,第二个串表示打补丁之后的状态(-'表示不存在,'+'表示存在,0表示不变).每个补丁都有一个执行时间,你的任务使用最少的时间把一个bug都存在的软件通过打补丁的方式变得没有bug.一个补丁可以打多次. 解法:状压表示每个补丁的存在与否.隐式

hdoj 1226 超级密码 【隐式图BFS】

题目:hdoj 1226 超级密码 分析:这题属于隐式图搜索,状态不是很明显,需要自己建立. 其实搜索说白了就是暴力. 这个题目就是,首先对给出的可以组成的所有的数依次枚举,长度从小到大. 比如第一组样例,因为0不能出现在首位,那么我们枚举首位为1 和 7 看看漫步满足, 满足的话枚举第二位10 11 17 以及 70 71 77 顺便保存他们取余 n 之后的值,这样就可以剪枝,搜索过的就不用重复搜索了. 要求最早出现的BFS即可,第一个搜到的就是. 注意长度不大于500 AC代码: #incl

倒水问题 (FillUVa 10603) 隐式图

题意:本题的题意是给你三个杯子,第一二个杯子是空的,第三个杯子装满水,要求是量出一定容量d升的水.若是得不到d升的水,那就让某一个杯子里面的水达到d',使得d'尽量接近d升. 解题思路:本题是给出初始状态,让你寻找一条通往目标的路径,此题也可看成是有向图中在起点和目标点之间寻找一条最短路径,但是这个最短路径不是距离最短而是倒的水最少,所以这题类似于Dijkstra算法求最短路,利用广搜,一直选当前水量最少的结点进行扩展,所以可以建立一个优先队列来存储下一次要访问的结点,同时将访问过的结点标记一下