路径寻找(隐式图遍历)

Posted tyner

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了路径寻找(隐式图遍历)相关的知识,希望对你有一定的参考价值。

八数码难题

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

编码和解码

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

以上是关于路径寻找(隐式图遍历)的主要内容,如果未能解决你的问题,请参考以下文章

BFS(路径寻找问题)

倒水问题 (FillUVa 10603) 隐式图

隐式图的遍历

代码:(bfs模板)立体推箱子

UVa10603 Fill (隐式图搜索+Dijkstra)

Ural 1741 Communication Fiend(隐式图+虚拟节点最短路)