题解 P2324 【[SCOI2005]骑士精神】

· · 题解

具体怎么做楼上大佬们说的很清楚orz,不过本蒟蒻有一个小小的操作←←。。

本蒟蒻特别喜欢位运算,所以当我看到棋盘只有5*5的时候,我马上写了个结构体:

class state

{
public:
    int map, x, y;
};

其中map在二进制下是压缩后的棋盘,1表示黑骑士,0表示白骑士。x,y即空位置的坐标。

举个例子: 对于样例: 10110 01*11 10111 01001 00000

压缩到map里 1011001*11101110100100000

‘*’可以是0或1,因为我们的x,y已经指出它是空位置了,那么这个位置上的值是0还是1是无意义的。

这么压缩有什么用呢?emmm,方便比较,方便赋值,更重要的是它看上去很快OvO

当然了,还要有一个配套的宏。

#define ITP(x, y) ((x) * 5 + (y))

这个宏用来将棋盘上的坐标(x,y)转换为map在二进制下的位数。

那么我们怎么判断当前状态now是否是最终状态呢?

now为当前状态,dest为预处理的最终状态

if (now.x == dest.x && now.y == dest.y)//首先它们空位置要相等
{
    if(now.map == dest.map || (now.map ^ (1 << ITP(now.x, now.y))) == dest.map)//如果两个map直接相等,或者是在空位置取反后相等(因为空位置的0,1被认为是等效的),则状态是相等的
        return 0;//状态相等,估价函数返回0
}

基于类似的想法,我的估价函数整个也写的相当朴素(也许粗糙贴切)(当然也不快,开了O2还要500ms),不过AC还是没问题的。

int need(state& now)
{
    if (now.x == dest.x && now.y == dest.y)
    {
        if(now.map == dest.map || (now.map ^ (1 << ITP(now.x, now.y))) == dest.map)
            return 0;
    }
    int ret(0);
    int x(now.map ^ dest.map);//取异或的结果就是不在自己该在的位置上的骑士为1(除了空位置的0,1不确定,这点比较粗糙)
    if (x & (1 << ITP(now.x, now.y)))//如果因为空位置导致ret多了1,应该减去,因为我们是乐观的……估价函数,而空位置是可以通过移动一个骑士的同时改变的
    {
        ret = -1;
    }
    while (x)
    {
        if (x & 1) ++ret;
        x >>= 1;
    }
    if (ret == 0) return 1;//这个……粗糙的处理,因为两个状态不可能相等的(已经判断过了),怎么可能ret==0……当然要返回1啦- -
    return ret;
}

接下来就是全部代码了T.T,除了上面的操作没有什么值得说的地方

#include <cstdio>
#include <cstdlib>
#include <cstring>

#define ITP(x, y) ((x) * 5 + (y))

class state
{
public:
    state() : map(0) {}
    state(const state& s) : map(s.map), x(s.x), y(s.y) {}
    int map;
    int x, y;
};

int t;

inline void readchar(char& c)
{
    c = getchar();
    while (c != '0' && c != '1' && c != '*') c = getchar();
}

state dest, now;

void init_dest()
{
    dest.x = dest.y = 2;
    for (int i(0); i != 2; ++i)
    {
        for (int j(i); j != 5; ++j)
        {
            dest.map |= 1 << ITP(i, j);
        }
    }
    for (int i(2); i != 4; ++i)
    {
        for (int j(i + 1); j != 5; ++j)
        {
            dest.map |= 1 << ITP(i, j);
        }
    }
}

int ans;

int need(state& now)
{
    if (now.x == dest.x && now.y == dest.y)
    {
        if(now.map == dest.map || (now.map ^ (1 << ITP(now.x, now.y))) == dest.map)
            return 0;
    }
    int ret(0);
    int x(now.map ^ dest.map);
    if (x & (1 << ITP(now.x, now.y)))
    {
        ret = -1;
    }
    if (x & (1 << ITP(dest.x, dest.y))) --ret;
    while (x)
    {
        if (x & 1) ++ret;
        x >>= 1;
    }
    if (ret == 0) return 1;
    return ret;
}

const int mx[8] = {1, 1, -1, -1, 2, 2, -2, -2};
const int my[8] = {2, -2, 2, -2, 1, -1, 1, -1};

bool dfs(int dep, state& now, int max_dep)
{
    if (dep + need(now)  > max_dep)
    {
        return false;
    }
    if (dep == max_dep)
    {
        return true;
    }
    for (int i(0); i != 8; ++i)
    {
        int nowx(now.x + mx[i]), nowy(now.y + my[i]);
        if (nowx < 0 || nowy < 0 || nowx > 4 || nowy > 4) continue;
        state next(now);
        if (bool(next.map & (1 << ITP(nowx, nowy))) != bool(next.map & (1 << ITP(next.x, next.y))))
        {
            next.map ^= (1 << ITP(next.x, next.y));
        }
        next.x = nowx;
        next.y = nowy;
        if (dfs(dep + 1, next, max_dep)) return true;
    }
    return false;
}

int main()
{
    init_dest();
    scanf("%d", &t);
    for (int i(0); i != t; ++i)
    {
        now.map = 0;
        for (int i(0); i != 5; ++i)
        {
            for (int j(0); j != 5; ++j)
            {
                char c;
                readchar(c);
                switch (c)
                {
                case '0':
                    break;

                case '1':
                    now.map |= 1 << ITP(i, j);
                    break;

                case '*':
                    now.x = i;
                    now.y = j;
                    break;
                }
            }
        }
        bool bFind(false);
        for (int i(0); i <= 15; ++i)
        {
            if (dfs(0, now, i))
            {
                printf("%d\n", i);
                bFind = true;
                break;
            }
        }
        if (!bFind) printf("-1\n");
    }
    return 0;
}

/////////////////////////5.23更新(加特技)

orz本蒟蒻带着无O2 60ms,开O2 0ms的题解回来了……当然,还有更骚的位操作和双向广搜。

这道题的数据十分微妙orz。

我们先把棋盘压缩成一个int,如之前所讲。

例子就是初始状态:

11111

01111

00011

00001

00000

压缩成0000010000110001111011111B(‭33000480D‬)

但这次强制规定:空位置的位必须为0

然后我们发现,坐标x,y的范围是0-4,正好3(*2)个二进制位……

于是,棋盘位数+坐标位数 == 25 + 6 == 31,正好压在了int的负数位前……

于是我们把初始状态压缩为0100100000010000110001111011111B(604529631D‬)

其中最前面6位表示了x,y,各占三位。

再写一个函数SetXY来帮我们设置x,y值:

inline void SetXY(int& state, int x, int y)
{
    state &= 33554431;
    state |= x << 25;
    state |= y << 28;
}

后面两句还好理解的。

但第一句出现了一个魔数:33554431。我们将它的二进制写出来:

‭1111111111111111111111111‬B(别数了,二十五个1)

所以任何数字对这个数取与后只剩下前25位了,即之前压缩的map。

再写一个函数取出x,y:

inline void GetXY(int& state, int& x, int& y)
{
    x = (state >> 25) & 7;
    y = (state >> 28) & 7;
}

右移25和28位大家都懂吧……把x,y的那三位移到最前面

其中又出现了一个魔数7,它的二进制是111B,即取最后3个。

好了,关于状态的说明完了。

然后写一个hash表来帮助双向广搜判断是否出现重复:

my_std::unordered_map final, exist;

emm,这样就很好啦!让我们双向广搜吧!【读者:这个unordered_map什么鬼】

关于这个unordered_map,定义在下面:

namespace my_std
{
    class unordered_map
    {
    protected:
        class item//链表的单个元素——hash冲突采用挂链处理
        {
        public:
            item() : next(0) {}
            int key, val;//键,值
            item*next;
        };

    public:
        unordered_map()//初始化头指针为0
        {
            for (int i(0); i != MAX_HASH; ++i) head[i] = 0;
        }

        item *newitem()
        {
            return &memory[next++];//内存管理。可能比new快……而且不用回收内存(下文会提到)
        }

        bool exist(int a) const//判断是否存在键为a的元素
        {
            int x(hash(a));
            if (head[x] == 0)
            {
                return false;
            }
            item* now(head[x]);
            while (now)
            {
                if (now->key == a) return true;
                now = now->next;
            }
            return false;
        }

        int& operator[](int a)//找到键为a的元素并返回其值的引用
        {
            int x(hash(a));
            if (head[x] == 0)
            {
                head[x] = newitem();
                head[x]->key = a;
                head[x]->val = 0;
                return head[x]->val;
            }
            item *now(head[x]), *front;
            while (now)
            {
                if (now->key == a) return now->val;
                front = now;
                now = now->next;
            }
            front->next = newitem();
            front->next->key = a;
            front->next->val = 0;
            return front->next->val;
        }

        void clear()//清空hash表
        {
            for (int i(0); i != MAX_HASH; ++i)
            {
                head[i] = 0;
            }
            next = 0;//注意,如果采用new创建元素,还应该遍历hash表delete所有元素以避免内存泄漏
            //当然,如果采用内存池的做法,重置next就好了
        }

    protected:

        int hash(int x) const { return x % MAX_HASH; }

        item *head[MAX_HASH];
        item memory[MAX_HASH];
        int next;//next是下一个可用的、未必为空的item
    };
}

好了,然后就该双向广搜了。(其实对于dest的广搜可以打表……而且打出来的表还都是int,非常完美)

双向广搜嘛,用hash表exist代替原来我们常用的bool访问标记,并且记录每个点的搜索深度(即需要走几步到达)如果搜到某个点出现在了final中(即对面广搜过来的状态),那么立即返回两个元素搜索深度之和

代码实现嘛……看下面的全部代码好了←←(本蒟蒻又开始懒了)

#include <cstdio>
#include <cstdlib>
#include <cstring>

#include <queue>

#define ITP(x, y) ((x) * 5 + (y))

#define MAX_HASH 42333

namespace my_std
{
    class unordered_map
    {
    protected:
        class item
        {
        public:
            item() : next(0) {}
            int key, val;
            item*next;
        };

    public:
        unordered_map()
        {
            for (int i(0); i != MAX_HASH; ++i) head[i] = 0;
        }

        item *newitem()
        {
            return &memory[next++];
        }

        bool exist(int a) const
        {
            int x(hash(a));
            if (head[x] == 0)
            {
                return false;
            }
            item* now(head[x]);
            while (now)
            {
                if (now->key == a) return true;
                now = now->next;
            }
            return false;
        }

        int& operator[](int a)
        {
            int x(hash(a));
            if (head[x] == 0)
            {
                head[x] = newitem();
                head[x]->key = a;
                head[x]->val = 0;
                return head[x]->val;
            }
            item *now(head[x]), *front;
            while (now)
            {
                if (now->key == a) return now->val;
                front = now;
                now = now->next;
            }
            front->next = newitem();
            front->next->key = a;
            front->next->val = 0;
            return front->next->val;
        }

        void clear()
        {
            for (int i(0); i != MAX_HASH; ++i)
            {
                head[i] = 0;
            }
            next = 0;
        }

    protected:

        int hash(int x) const { return x % MAX_HASH; }

        item *head[MAX_HASH];
        item memory[MAX_HASH];
        int next;
    };
}

my_std::unordered_map final, exist;

int t;

inline void readchar(char& c)
{
    c = getchar();
    while (c != '0' && c != '1' && c != '*') c = getchar();
}

int dest(604529631), now;

inline void SetXY(int& state, int x, int y)
{
    state &= 33554431;
    state |= x << 25;
    state |= y << 28;
}

inline void GetXY(int& state, int& x, int& y)
{
    x = (state >> 25) & 7;
    y = (state >> 28) & 7;
}

const int mx[8] = { 1, 1, -1, -1, 2, 2, -2, -2 };
const int my[8] = { 2, -2, 2, -2, 1, -1, 1, -1 };

void init_dest()
{
    final[dest] = 0;
    std::queue<int> que, dep;
    que.push(dest);
    dep.push(0);
    while (!que.empty())
    {
        int now(que.front());
        int depth(dep.front());
        que.pop();
        dep.pop();
        if (depth == 9)
        {
            continue;
        }
        int nowx, nowy;
        GetXY(now, nowx, nowy);
        for (int i(0); i != 8; ++i)
        {
            int nx(nowx + mx[i]), ny(nowy + my[i]);
            if (nx < 0 || ny < 0 || nx > 4 || ny > 4) continue;
            int next(now);
            if (next & (1<<ITP(nx, ny)))
            {
                next ^= (1 << ITP(nowx, nowy)) | (1 << ITP(nx, ny));
            }
            SetXY(next, nx, ny);
            if (!final.exist(next))
            {
                final[next] = depth + 1;
                que.push(next);
                dep.push(depth + 1);
            }
            else
            {
                next = next + 1 - 1;
            }
        }
    }
}

int ans;

int bfs()
{
    exist.clear();
    exist[now] = 0;
    std::queue<int> que, dep;
    que.push(now);
    dep.push(0);
    while (!que.empty())
    {
        int now(que.front());
        int depth(dep.front());
        que.pop();
        dep.pop();
        if (final.exist(now))
        {
            return depth + final[now];
        }
        if (depth == 6) continue;
        int nowx, nowy;
        GetXY(now, nowx, nowy);
        for (int i(0); i != 8; ++i)
        {
            int nx(nowx + mx[i]), ny(nowy + my[i]);
            if (nx < 0 || ny < 0 || nx > 4 || ny > 4) continue;
            int next(now);
            if (next & (1 << ITP(nx, ny)))
            {
                next ^= (1 << ITP(nowx, nowy)) | (1 << ITP(nx, ny));
            }
            if (next & (1 << ITP(nx, ny))) next ^= (1 << ITP(nx, ny));
            SetXY(next, nx, ny);
            if (!exist.exist(next))
            {
                exist[next] = depth;
                que.push(next);
                dep.push(depth + 1);
            }
        }
    }
    return -1;
}

int main()
{
    init_dest();
    scanf("%d", &t);
    for (int i(0); i != t; ++i)
    {
        now = 0;
        for (int i(0); i != 5; ++i)
        {
            for (int j(0); j != 5; ++j)
            {
                char c;
                readchar(c);
                switch (c)
                {
                case '0':
                    break;

                case '1':
                    now |= 1 << ITP(i, j);
                    break;

                case '*':
                    SetXY(now, i, j);
                    break;
                }
            }
        }
        printf("%d\n", bfs());
    }
    return 0;
}