ZOJ 3814 Sawtooth Puzzle (2014年牡丹江赛区网络赛F题)

Posted yangykaifa

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ZOJ 3814 Sawtooth Puzzle (2014年牡丹江赛区网络赛F题)相关的知识,希望对你有一定的参考价值。

1.题目描写叙述:点击打开链接

2.解题思路:本题是一道隐式图的搜索题目。一般来说,这类题目首先要定义状态,接下来是弄清楚状态怎样转移,以及状态怎样判重,怎样推断当前状态是否和目标状态同样。至于求解最短路就是经常使用的BFS就可以。

接下来我们逐一展开讨论。


1.状态的定义:看到这道题,猛一下会想着把每一个字符分别用01表示,然后看成二进制码进行状态压缩,这个状态定义尽管能够,可是显然,状态过于精确和复杂,假设把一行给压缩成一个整数,那么一个完整的图案要用8*9。即72个数才干描写叙述。显然过于庞大。并且对于状态的转移也变得异常复杂。因此这不是一个好的定义。假设我们注意到每一个方块的图案是刚性的这一特定,就会发现。事实上重要的不是图案本身的样子,而是每一个图案相对于初始状态中的图案旋转了多少次。也就是说,我们仅仅须要用0~3就可以准确地描写叙述第i个方块相对于初始状态旋转的次数。这样。我们能够用9个,范围在0~3的整数来描写叙述一个完整的图案。并且,我们发现。这样定义有一个很大的优点就是旋转操作变得很easy,假设是顺时针旋转就是0->1,1->2,2->3,3->0,逆时针反之。因此。这样的定义能够作为一个好的定义。


2.状态的转移:依据题意描写叙述,我们每次都要选择某一块将它顺时针旋转。然后。假设这个方块和相邻的方块之间有齿轮耦合,那么相邻的方块也会被带动旋转。以此类推。

也就是,一个方块的旋转可能会带动多个方块同一时候旋转,看上去挺棘手的。这时我们能够考虑用一下常量数组来处理。由于我们在最初输入的时候还输入了每一个方块上下左右是否具有齿轮。只是要注意,这个“左上右下”是针对初始状态来说的,那么假设我们把第i个方块顺时针旋转90度。此时,新状态的“左上右下”就是原初始状态中的“下左上右”。

再旋转90度。就变成初始状态的“右下左上”,以此类推。

即我们能够设一个状态转移数组来表示这样的情况。用tp[i][j]表示当状态为i(此时的i取值范围是0~3)时,它的第j个下标相应的初始齿轮数组的下标应该是多少(下标从1開始,注意和状态的取值范围分开)。依照刚刚的讨论,tp[0][1]=1,tp[0][2]=2,tp[0][3]=3,tp[0][4]=4。tp[1][1]=4,tp[1][2]=1,tp[1][3]=2,tp[1][4]=3。

        设置好了这个常量数组后。我们为了在O(1)时间内获得第i个方块的上下左右相应方块的编号,能够再设置4个常量数组。

分别用up[i],down[i],lef[i],rig[i]来表示。这个数组不难写出。具备了这5个常量数组。rotate函数就很好写了:首先让第i个方块顺时针旋转90度(即相应的值+1就可以),然后用常量数组获得它上下左右的方块编号。用meshed函数推断这4个方块(假设存在的话)是否和第i个方块有齿轮耦合。有的话置1。接下来对于有齿轮耦合的。对它们进行rotate操作。同一时候用vis[i]来标记是否操作过。为了便于表示顺时针和逆时针,我们分别用1。2来表示。比方第i块旋转的编号是flag。那么跟它相邻的就变成3-flag。至于meshed函数,我们能够用上tp数组迅速获得相应的齿轮下标。来确定是否有耦合。

这样,运行完rotate。就完毕了状态转移。


3.状态的判重:我们发现,一共仅仅有9个数。每一个数的变化范围是0~3,因此最好还是写一个Hash函数,将这9个数压缩成一个唯一的整数。

我们发现。0~3占领了2个bit。那么仅仅须要每次把一个数平移2i位就可以。假设下标从1開始。就是2*(i-1)位(第一个数不用移动)。

最多仅仅须要平移16位就可以。

仍然在int范围之内。

并且得到的Hash值一定不会出现反复,能够唯一的表示一个完整的状态。假设我们是通过d数组(BFS中的距离数组)来判重的。那么先初始化为INF。仅仅须要看d[v](v是状态相应的Hash值)是否为INF就可以实现判重。


4.是否到达目标状态:注意,这一步不要和状态判重混淆,由于本题的目标状态实际上能够用多对状态值去描写叙述,而不能够简单的用Hash是否同样来推断。比方题目中的第5块方块。无论它怎样旋转,图案都是一样的。也就是说,第5个状态值是0~3均可能是目标状态的一部分。这就提示我们须要另外找一种推断方法。那么怎样做呢?依据状态的定义。我们能够首先把每一块都尝试旋转4次(不考虑齿轮耦合),看当前旋转的状态是否和目标状态中对应的方块图案全然一样(即这次我们须要用上输入的字符)。

一样的话,就把它标记为1。

假设我们用一个cor数组标记,那么cor[i][j]=1就表示第i个方块顺时针旋转j次后和目标状态的第i块图案一样。这样预处理后,推断函数就不难写了。仅仅须要看对全部的i。是否有cor[i][st[i]]=1就可以(st[i]表示第i个状态值分量)。


至此。我们顺利的攻克了这4大隐式图搜索的问题,剩余的找最短路就是普通的BFS就可以实现,从而完美的攻克了本题。本题对于状态的定义是问题的关键。常量数组的设置也很值得我们学习。

3.代码:

//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<iostream>
#include<algorithm>
#include<cassert>
#include<string>
#include<sstream>
#include<set>
#include<bitset>
#include<vector>
#include<stack>
#include<map>
#include<queue>
#include<deque>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<ctime>
#include<cctype>
#include<functional>
using namespace std;

#define me(s)  memset(s,0,sizeof(s))
typedef long long ll;
typedef unsigned int uint;
typedef unsigned long long ull;
typedef pair <int, int> P;

struct State
{
    int st[11];
    State(){}
    State(int a1,int a2,int a3,int a4,int a5,int a6,int a7,int a8,int a9)
    {
        st[1]=a1,st[2]=a2,st[3]=a3,st[4]=a4,st[5]=a5,st[6]=a6,st[7]=a7,st[8]=a8,st[9]=a9;
    }
};
const int INF=100000000;
const int up[]={0,0,0,0,1,2,3,4,5,6};
const int down[]={0,4,5,6,7,8,9,0,0,0};
const int lef[]={0,0,1,2,0,4,5,0,7,8};
const int rig[]={0,2,3,0,5,6,0,8,9,0};
const int tp[4][5]={{0,1,2,3,4},{0,4,1,2,3},{0,3,4,1,2},{0,2,3,4,1}};
queue<State>q;
string s[10][10],t[10][10],p[10];
int st[11][11];
int vis[11];
int cor[11][11];
int d[1<<20];
int ans;

void init_initial()
{
    for(int i=0;i<3;i++)
        for(int j=1;j<=8;j++)
        cin>>s[i*3+1][j]>>s[i*3+2][j]>>s[i*3+3][j];
}

void init_target()
{
    for(int i=0;i<3;i++)
        for(int j=1;j<=8;j++)
        cin>>t[i*3+1][j]>>t[i*3+2][j]>>t[i*3+3][j];
}

int Hash(State x)//对状态x进行压缩
{
    int res=0;
    for(int i=1;i<=9;i++)
        res=res+(x.st[i]<<(2*(i-1)));
    return res;
}

bool meshed(State x,int a,int e1,int b,int e2)//推断第a个方块的e1方位和第b个方块的e2方位是否都有齿轮
{
    int f1=st[a][tp[x.st[a]][e1]],f2=st[b][tp[x.st[b]][e2]]; //利用tp数组高速获得相应下标
    return f1==1&&f2==1;   //都是1,说明有齿轮耦合
}

State rotate(State x,int y,int flag)
{
    vis[y]=1;
    int l=lef[y],r=rig[y],u=up[y],d=down[y];
    bool fl=0,fu=0,fd=0,fr=0;
    if(l&&!vis[l]&&meshed(x,l,3,y,1))fl=1;
    if(r&&!vis[r]&&meshed(x,r,1,y,3))fr=1;
    if(u&&!vis[u]&&meshed(x,u,4,y,2))fu=1;
    if(d&&!vis[d]&&meshed(x,d,2,y,4))fd=1;
    int z=x.st[y];
    if(flag==1) z=(z==3)?0:z+1;  //对第y块方块顺时针旋转90度
    else  z=(z==0)?3:z-1;
    x.st[y]=z;
    if(fl&&!vis[l])//假设第y块方块的左边发生齿轮耦合,且它没有旋转过,那么对左側的方块进行旋转
    {
        vis[l]=1;
        x=rotate(x,l,3-flag);
    }
    if(fr&&!vis[r])
    {
        vis[r]=1;
        x=rotate(x,r,3-flag);
    }
    if(fu&&!vis[u])
    {
        vis[u]=1;
        x=rotate(x,u,3-flag);
    }
    if(fd&&!vis[d])
    {
        vis[d]=1;
        x=rotate(x,d,3-flag);
    }
    return x;
}

bool ok(State y)//利用cor数组实现对到达目标状态的推断
{
    for(int i=1;i<=9;i++)
        if(!cor[i][y.st[i]])return 0;
    return 1;
}

void bfs(State x)
{
    while(!q.empty())q.pop();
    q.push(x);
    while(!q.empty())
    {
        x=q.front();q.pop();
        int sx=Hash(x);
        for(int i=1;i<=9;i++)
        {
            me(vis);
            State y=rotate(x,i,1);
            int sy=Hash(y);
            if(d[sy]>d[sx]+1)
            {
                d[sy]=d[sx]+1;
                if(ok(y))
                {
                    ans=d[sy];
                    return;
                }
                q.push(y);
            }
        }
    }
}

bool same(int x)
{
    for(int i=1;i<=8;i++)
        for(int j=0;j<8;j++)
        if(s[x][i][j]!=t[x][i][j])return 0;
    return 1;
}
void change(int x)  //对第x个方块旋转90度(不考虑齿轮耦合)
{
    for(int i=1;i<=8;i++)p[i]="";
    for(int i=1;i<=8;i++)
        for(int j=8;j>=1;j--)
        p[i]=p[i]+s[x][j][i-1];
    for(int i=1;i<=8;i++)
    s[x][i]=p[i];
}

int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        init_initial();  //初始化初始状态
        init_target();   //初始化目标状态
        for(int i=1;i<=9;i++)
            for(int j=1;j<=4;j++)
            scanf("%d",&st[i][j]); //齿轮数组
        fill(d,d+(1<<20),INF);   //把全部的距离都设为INF
        d[0]=0,ans=-1;
        me(cor);
        for(int i=1;i<=9;i++)
            for(int j=0;j<4;j++)  //对每一个方块的第j次旋转是否等于目标状态进行预处理
        {
            if(same(i))cor[i][j]=1;
            change(i);
        }
        bfs(State(0,0,0,0,0,0,0,0,0)); //利用BFS求最短路
        printf("%d\n",ans);
    }
}


以上是关于ZOJ 3814 Sawtooth Puzzle (2014年牡丹江赛区网络赛F题)的主要内容,如果未能解决你的问题,请参考以下文章

ZOJ2836-Number Puzzle-容斥原理

[ZOJ3435]Ideal Puzzle Bobble

The Last Puzzle ZOJ - 3541

Fabric 和 Sawtooth 技术分析(下)

TOJ 4689: Sawtooth

Hyperledger Fabric 和 Sawtooth 有啥区别? [关闭]