N-Pullze问题:Zobrist Hash & 逆序对

本文围绕N-Puzzle问题展开,介绍了Zobrist Hash方法,它通过特殊置换表对棋盘编码,可判断棋局新状态是否存在,还说明了如何从一个状态的Zobrist值得到后继状态的值。同时探讨了N-Puzzle问题的可解性判断,依据初始状态和末状态逆序数奇偶性及相关规则来判定,还提及逆序数的求法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、Zobrist Hash(By 15-Puzzle problem)

目的:使用Zobrist Hash判断N-Pullze棋局新生成的状态是否存在;

​ 如何从一个状态的Zobrist值得到其后继状态的Zobrist值;

Zobrist Hash思想:

Zobrist hash是一种专门针对棋类游戏而提出来的编码方式, Zobrist哈希通过一种特殊的置换表,也就是对棋盘上每一位置的各个可能状态赋予一个编码索引值,来实现在极低冲突率的前提下在一个整型数据上对棋盘进行编码。

以15-Puzzle为例说明:

题意:4*4棋盘,由15个方块和一个空位组成。15个棋子,每个棋子上标有1至15的某一数字;空格用0表示;空格周围的棋子可以移动到空格中。移动方块,让所有的方块按照数字的顺序排列。(以下讨论将0也算入其中)

一共4x4x16种组合;设定三维数组

Z[4][4][16] //每一个产生一个随机数,至少保证是32位的长度(即32bit),最好是64位。一般我们设定为32位,很多时候为了校验还会再设定一个64位的随机数。
    
//Z[0][0][0]——Z[0][0][15]的值表示0-15在(0,0)的每个状态的随机数;

对于一个特定的棋局,将每个单位的状态对应的随机数作异或(^)运算,所得值即为Hash值;

对于生成的初始状态,判断新生成的状态是否有与原来的生成的初始状态的Hash值是否一致。若一致,则新生成的初始状态已经存在。

对于判断是否与某历史状态是否存在同理。

如何从一个状态的Zobrist值得到其后继状态的Zobrist值?

当前状态会得到一个Zobrist值a;

比如(0,0)位置上是8,(0,1)位置上是0;

操作:将8移到(0,1);

​ 0到(0,0);

//原来键值里异或过Z[0][0][8]  和 Z[0][1][0];
//再次异或这个值,棋子即从键值中删除;再异或新的状态;所以后继状态的Zobrist为
a1=a^Z[0][0][8]^Z[0][1][0]^Z[0][0][0]^Z[0][1][8];

参考:https://www.cnblogs.com/IThaitian/p/3616507.html

​ &&队友的代码就不贴了

2、N-Puzzle问题的可解性判断

末状态逆序对:0

初始状态:8-Puzzle ;

​ 3x3; 3为奇数;

上下每交换一次逆序数改变偶次数,左右交换不变

初始状态的逆序数与末状态逆序数奇偶性相同则可达;

​ 15-Puzzle;

​ 4x4; 4为偶数;

上下每交换一次逆序数改变奇次数,左右交换不变;

初始状态的逆序数+初始0到最低行的行距(0最后在最低行)与末状态奇偶性相同即可互相到达;

延申:MxN Puzzle问题同理

逆序数求法:归并排序&树状数组

归并排序原理:分治法

图来源:https://www.cnblogs.com/chengxiao/p/6194356.html

在这里插入图片描述

合并:
在这里插入图片描述
在这里插入图片描述

Code:

#include<bits/stdc++.h>
#define maxn 100
int a[maxn],b[maxn];
using namespace std;
void merge_sort(int l,int r){
    if(r-l>0){
        int mid=(l+r)/2;
        int i=l;
        int p=l;
        int q=mid+1;
        merge_sort(l,mid);
        merge_sort(mid+1,r);
        while(p<=mid||q<=r){  //左右两部分只要有一部分不为空
            if(q>r||(p<=mid&&a[p]<=a[q])){
                b[i++]=a[p++];//q>r 即将左边剩的复制到数组,否则比较
            }
            else{
                b[i++]=a[q++]; //将右半数组复制到辅助数组
            }
        }
        for(i=l;i<=r;i++){
            a[i]=b[i]; //拷贝
        }
    }
}
int main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    merge_sort(1,n);
    for(int i=1;i<n;i++){
        printf("%d ",a[i]);
    }
    cout<<a[n]<<endl;

return 0;
}

求逆序对原理:

左右两部分合并时,当右指针数字小于左指针的时候,他会比从左指针开始到中间的元素个数个逆序对。也就是mid-l+1;

所以添加一行代码即可求出逆序对:

#include<bits/stdc++.h>
#define maxn 100
int a[maxn],b[maxn];
int cnt;
using namespace std;
void merge_sort(int l,int r){
    if(r-l>0){
        int mid=(l+r)/2;
        int i=l;
        int p=l;
        int q=mid+1;
        merge_sort(l,mid);
        merge_sort(mid+1,r);
        while(p<=mid||q<=r){  //左右两部分只要有一部分不为空
            if(q>r||(p<=mid&&a[p]<=a[q])){
                b[i++]=a[p++];//q>r 即将左边剩的复制到数组,否则比较
            }
            else{
                b[i++]=a[q++]; //将右半数组复制到辅助数组
                cnt+=mid-p+1;
            }
        }
        for(i=l;i<=r;i++){
            a[i]=b[i]; //拷贝
        }
    }
}
int main(){
    int n;
    cnt=0;
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
    }
    merge_sort(1,n);
    for(int i=1;i<n;i++){
        printf("%d ",a[i]);
    }
    cout<<a[n]<<endl;
    cout<<cnt<<endl;

return 0;
}

树状数组求逆序对:

树状数组原理:

参考https://blog.youkuaiyun.com/Small_Orange_glory/article/details/81290634

Code:

//单点更新
void update(int x,int y,int n){
    for(int i=x;i<=n;i+=lowbit(i))    //x为更新的位置,y为更新后的数,n为数组最大值
        c[i] += y;
}
//区间查询
int getsum(int x){
    int ans = 0;
    for(int i=x;i;i-=lowbit(i))
        ans += c[i];
    return ans;
}

求逆序对原理:

https://blog.youkuaiyun.com/ssimple_y/article/details/53744096

#include<bits/stdc++.h>
#define maxn 1007
using namespace std;
int aa[maxn];//离散化后的数组
int c[maxn]; //树状数组
int n;

struct Node
{
    int v;
    int order;
}a[maxn];

bool cmp(Node a, Node b)
{
    return a.v < b.v;
}

int lowbit(int k)
{
    return k&(-k); //基本的lowbit函数
}

void update(int t, int value)
{     //即一开始都为0,一个个往上加(+1),
    int i;
    for (i = t; i <= n; i += lowbit(i))
        c[i] += value;
}

int getsum(int t)
{  //即就是求和函数,求前面和多少就是小于它的个数
    int i, sum = 0;
    for (i = t; i >= 1; i -= lowbit(i))
        sum += c[i];
    return sum;
}

int main()
{
    int i;
    while (scanf("%d", &n), n)
    {
        for (i = 1; i <= n; i++) //离散化
        {
            scanf("%d", &a[i].v);
            a[i].order = i;
        }
        sort(a + 1, a + n + 1,cmp);//从1到n排序,cmp容易忘
        memset(c, 0, sizeof(c));
        for (i = 1; i <= n; i++)
            aa[a[i].order] = i;
        int ans = 0;
        for (i = 1; i <= n; i++)
        {
            update(aa[i], 1);
            ans += i - getsum(aa[i]); //减去小于的数即为大于的数即为逆序数
        }
        printf("%d\n", ans);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值