P5507 题解

前言

题目传送门!

更好的阅读体验?

爆搜题。大家都不用 Astar 算法,那我来补篇题解。

思路

每个按钮都有状态,因此考虑状压爆搜。

每一个按钮都有 \(1, 2, 3, 4\) 几种状态。因此可以转化为 \(0, 1, 2, 3\) 四种状态,然后两位一状压。

接下来就是 A 星。估值函数 \(h(x)\) 可以设计为:每个按钮分别旋转到 \(1\) 的值。由于一次操作会牵动两个按钮,因此要除以二。

//为了方便状压,数组、按钮编号,下标都从1开始
struct node
{
	int st; //状态 
	double f;
	node(int zltqwq)
	{
		db h = 0;
		st = zltqwq;   //下面 &3 可以简单理解为 mod 4
		for (int i = 0; i < 12; i++) h += (4 - ((st >> (i * 2)) & 3)) & 3; 
		h /= 2;
		f = g[st] + h;	
	}
	friend bool operator <(node y, node x) {return x.f < y.f;}
};
priority_queue <node> q;

定义好后,输入时记录一下初始状态。然后开始爆搜。

int IAKIOI; //想不到好的名字了。反正是初始状态
void input()
{
	for (int i = 0; i < 12; i++)
	{   //下标统一减一
		int a = read() - 1; IAKIOI |= (a << (i * 2));
		for (int j = 0; j < 4; j++) nxt[i][j] = read() - 1; 
	}	
}

每次枚举每一个状态下,对应的每一个按钮的状态。同时记录每一个按钮受牵动的按钮编号及其对应状态。

然后通过异或性质(\(x \oplus x = 0\))将下一个状态求出。这一部分其实很容易,只是代码实现看起来比较罢了。

这个状态没有访问过就更新。由于最后要输出方案,所以记录一下状态是由哪一个状态,旋转什么按钮得到的。

int IAKIOI; //初始状态 
void Astar()
{
    priority_queue <node> q;
    q.push(node(IAKIOI)), vis[IAKIOI] = true;
    while (!q.empty())
    {
        int st = q.top().st;
        //printf("st = %d\n", st);
        q.pop();
        if (st == 0) break; //等同于全是1
        for (int i = 0; i < 12; i++)
        {
        	//分别表示i的状态,受牵连的按钮编号,受牵连的按钮的状态
            int sti = (st >> (i * 2)) & 3, di = nxt[i][sti], stdi = (st >> (di * 2)) & 3; 
            //其实就是将第i个按钮替换成下一个状态。当前状态为 sti,下一个状态显然是 (sti + 1) % 4
         	int dst = st ^ (sti << (i * 2)) ^ (((sti + 1) & 3) << (i * 2));
         	//同上,就是将第di个按钮替换成下一个状态。当前状态为 stdi,下一个状态显然是 (stdi + 1) % 4
            dst = dst ^ (stdi << (di * 2)) ^ (((stdi + 1) & 3) << (di * 2));
            if (!vis[dst]) //没有就进去
            {
				//pre[] 和 button[] 分别表示:从哪个状态过来;从那个状态过来是使用什么按钮 
                g[dst] = g[st] + 1, vis[dst] = true;
                pre[dst] = st, button[dst] = i + 1;
                q.push(node(dst));
            }
        }
    }
}

然后输出就完事了。用递归的形式倒序输出即可。

void output(int x)
{
	if (x == IAKIOI) return;
	output(pre[x]), write(button[x]), space;
}
int main()
{
	input();
	Astar();
	write(g[0]), endl; //0是最终状态
	output(0);
	return 0;
}

这道题就做完啦!

完整代码

#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
int read()
{
	char op = getchar(); int x = 0, f = 1;
	while (op < 48 || op > 57) {if (op == '-') f = -1; op = getchar();}
	while (48 <= op && op <= 57) x = (x << 1) + (x << 3) + (op ^ 48), op = getchar();
	return x * f;
}
const int k = 1 << 24;
int nxt[15][10], g[k + 10];
bool vis[k + 10];
int pre[k + 10], button[k + 10]; //分别表示:从哪个状态过来;从那个状态过来是使用什么按钮 
struct node
{
	int st; //状态 
	double f;
	node(int zltqwq) //变量起 @zltqwq 是因为想不到好名字了。膜拜 zlt
	{
		double h = 0;
		st = zltqwq;
		for (int i = 0; i < 12; i++) h += (4 - ((st >> (i * 2)) & 3)) & 3;
		h /= 2;
		f = g[st] + h;	
	}
	friend bool operator <(node y, node x) {return x.f < y.f;}
};
int IAKIOI; //初始状态 
void Astar()
{
	priority_queue <node> q;
	q.push(node(IAKIOI)), vis[IAKIOI] = true;
	while (!q.empty())
	{
		int st = q.top().st;
		//printf("st = %d\n", st);
		q.pop();
		if (st == 0) break; //等同于全是1
		for (int i = 0; i < 12; i++)
		{
			int sti = (st >> (i * 2)) & 3, di = nxt[i][sti], stdi = (st >> (di * 2)) & 3;
			int dst = st ^ (sti << (i * 2)) ^ (((sti + 1) & 3) << (i * 2));
			dst = dst ^ (stdi << (di * 2)) ^ (((stdi + 1) & 3) << (di * 2));
			if (!vis[dst])
			{
				g[dst] = g[st] + 1, vis[dst] = true;
				pre[dst] = st, button[dst] = i + 1;
				q.push(node(dst));
			}
		}
	}
}
void input()
{
	for (int i = 0; i < 12; i++)
	{
		int a = read() - 1;
        IAKIOI |= (a << (i * 2));
		for (int j = 0; j < 4; j++) nxt[i][j] = read() - 1;
	}	
}
void output(int x)
{
	if (x == IAKIOI) return;
	output(pre[x]), printf("%d ", button[x]);
}
int main()
{
	input();
	Astar();
	printf("%d\n", g[0]); //0是最终状态
	output(0);
	return 0;
}

希望能帮助到大家!

posted @ 2022-09-02 10:24  liangbowen  阅读(39)  评论(0编辑  收藏  举报