5507 机关

5507 机关

题意:
有12个旋钮,每个旋钮开始时处于状态 1~4,每次操作可以往规定方向转动一个旋钮(1⇒2⇒3⇒4⇒1)
并且会触发一次连锁反应:处某个状态的旋钮在旋转时会引起另一个旋钮发生相同方向的转动(另一个旋钮转动不会再触发连锁反应)。问将12个旋钮都置为 11 至少需要几次转动,并输出每次转动的旋钮编号
这个题有两种做法,双向BFS和启发式搜索

双向BFS

其实也可以简单暴力的BFS,每次转动都有12种选择,时间复杂度会炸
那么BFS会炸,是因为搜索树太大了,并且这个题的起始状态和终止状态都是明确的,我们使用神奇的双向BFS了,来限制搜索树的生长

双向 BFS 非常适合在起始状态和终止状态明确的条件下使用,他的做法是从起点和终点同时进行BFS,让两边BFS搜索树的生长受对面搜索树的限制,不要过于野蛮的生长,偏离目标太远
双向BFS的示意图

可以看到双向BFS可以在某一状态相同就停止了
通过回溯可以找到沿路选择的点
然后在存储的时候用状态压缩来存储,把两个二进制位做成一个四进制位

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
#include<queue> 
using namespace std;
inline int read(){
    int sum = 0,fu = 1;char ch = getchar();
    while(!isdigit(ch)) { if(ch == '-') fu = -1;ch = getchar();}
    while(isdigit(ch)) { sum = (sum<<1)+(sum<<3)+(ch^48);ch =getchar();} return sum * fu;
}
const int N = 1<<24;
bool vis[N];
int nxt[14][6],fa[N],choice[N],v[N],flag,m1,m2,mid,ans1[30],ans2[30];
queue<int>q;
int main(){
    int button,Start = 0;
    for(int i=0;i<=11;i++)
	{
        button = read();//读入按钮状态 
        Start |= (button - 1) << (i << 1);//记录初始状态 
        for(int j=0;j<=3;j++)
			 nxt[i][j]=read()-1;
    }
    vis[Start]=vis[0] = 1; //标记访问 
    v[Start] = 1;   
	v[0] = 2;
    q.push(0);
    while(!q.empty() && !flag)
	{
        int state = q.front(),direction = v[state];
        q.pop();
        int si,sNext,nx,nextState;
        for(int i=0;i<=11;i++)
		{
            if(direction == 1)
			{  //正向
                si = (state >> (i << 1))&3;   //1、获取第i+1个旋钮状态(0~3)
                nx = nxt[i][si];                       //2、获取牵连旋钮编号
                sNext = (state >> (nx << 1)) & 3;      //3、获取牵连旋钮状态,方式同1
                nextState = state ^ (si << (i << 1)) ^ (((si + 1) & 3) << (i << 1)); //4、修改状态为第i+1个旋钮旋转后的状态
                nextState ^= (sNext << (nx << 1)) ^ (((sNext + 1) & 3) << (nx << 1)); //5、修改状态为牵连旋钮旋转后的状态
            } else{                      //反向
                si = (state >> (i << 1))&3;
                nx = nxt[i][(si+3)&3];         //获取第i+1个旋钮逆向旋转后的牵连旋钮编号
                sNext = (state >> (nx << 1)) & 3;
                nextState = state ^ (si << (i << 1)) ^ (((si + 3) & 3) << (i << 1)); //修改状态为第i+1个旋钮逆向旋转后的状态
                nextState ^= (sNext << (nx << 1)) ^ (((sNext + 3) & 3) << (nx << 1));//修改状态为牵连旋钮逆向旋转后的状态
            }
            //如果这个状态在之前访问过
            if(vis[nextState]){
                if(v[nextState] == direction) continue;  //同方向的直接跳过,之前到达的时候肯定不劣于现在
                m1 = direction == 1 ? state : nextState;// m1 记录正向与逆向的连接点
                mid = i+1;
                m2 = direction == 1 ? nextState : state;//m2 记录逆向与正向的连接点
                flag = 1;break;
            }
            vis[nextState]=1;
            v[nextState]=direction; //继承方向
            fa[nextState]=state;//回溯 
            choice[nextState]=i+1;//记录本次的操作 
            q.push(nextState);
        }
    }
    int cnt1=0,state=m1,cnt2=0;
    //正向回溯
    while(state!=Start)
	{
        ans1[++cnt1] = choice[state];
        state = fa[state];
    }
    //逆向回溯
    state = m2;
    while(state != 0)
	{
        ans2[++cnt2] = choice[state];
        state = fa[state];
    }
    printf("%d\n",cnt1+cnt2+1);//计算答案 
    for(int i = cnt1; i; i--) 
		printf("%d ", ans1[i]);
    printf("%d ",mid);
    for(int i=1;i<=cnt2;i++)
		printf("%d ", ans2[i]);
    return 0;
}

启发式搜索

在 A* 算法中,我们要利用当前状态的信息对状态进行评价,以此来决定下一次的操作,极大地限制了搜索树的生长
(所以,树的生长是时间的关键)
我们使用F来表示x状态的代价:F(x)=g(x)+h*(x),其中g(x)表示从初始状态到当前状态付出的最小代价,本题中就是操作步数
h*(x)是从当前状态到目标状态走最佳路径付出的代价
所以我们是用h函数进行估计的

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cstring>
using namespace std;
#define For(a,sta,en) for(int a = sta;a <= en;a++)
inline int read(){
    int sum = 0,fu = 1;char ch = getchar();
    while(!isdigit(ch)) { if(ch == '-') fu = -1;ch = getchar();}
    while(isdigit(ch)) { sum = (sum<<1)+(sum<<3)+(ch^48);ch =getchar();} return sum * fu;
}
const int N = 1<<24;
int g[N],nxt[14][6],fa[N],ans[30],choice[N];
struct node{
    int state;   //状态
    double F;  //状态对应估价函数值
    node(int s):state(s){  //构造函数,冒号后面部分相当于 state = s;
        double h = 0;
        F = 0;
        For(i,0,11) if((s>>(i<<1))&3) h += 4 - ((s >> (i << 1)) & 3); //计算不处在状态1的旋钮的对应的h值
        F =  h * 1.2 + g[s];   //可以在h/2前乘一个玄学系数
    }
    bool operator<(const node &y) const{
        return F > y.F;  //估价函数值小的放前面
    }
};
priority_queue<node>q;

int main(){
    int button,Start = 0;
    For(i,0,11){
        button = read();                             //读入第i+1个旋钮状态
        Start |= (button - 1) << (i << 1);      //记录初始状态
        For(j,0,3) nxt[i][j] = read()-1;
    }
    q.push(node(Start));  //调用构造函数,顺便计算出估价函数值
    g[Start] = 0;
    while(!q.empty()){
        int state = q.top().state;
        q.pop();
        if(state == 0) break;
        int si,sNxt,nx,nextState;
        For(i,0,11){
            si = (state>>(i<<1))&3;
            nx = nxt[i][si];
            sNxt = (state>>(nx<<1))&3;
            nextState = state ^ (si << (i << 1)) ^ (((si + 1) & 3) << (i << 1)) ^ (sNxt << (nx << 1)) ^ (((sNxt + 1) & 3) << (nx << 1));
            //如果没有访问过就可以转移新状态了
            if(!g[nextState]){
                g[nextState] = g[state] + 1;
                fa[nextState] = state;      //用于回溯
                choice[nextState] = i + 1;
                q.push(node(nextState));
            }
        }
    }
    int cnt = 0,state = 0;
    while(state != Start){
        ans[++cnt] = choice[state];
        state = fa[state];
    }
    printf("%d\n",cnt);
    for(int i = cnt;i;i--) printf("%d ",ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值