枚举-拨钟问题



算法思路:

假设时钟指针位置对应的值为clock_time,那么顺时针旋转90°就是clock_time = (clock_time+1)%4    这一组时针就用一个数组表示。9种操作对应一个二维数组。


这一题实质类似熄灯问题和画家问题。其共通点在于:操作对环境的改变是无序的,每个操作都会影响到周围的状态。同时每一种操作都有周期性限制,也即最多需要几次操作,多于这个次数产生循环。

熄灯问题中,每个灯最多熄灯一次,因为灯只有两种状态,并且循环。而这里,有4种循环的状态,因此每个移动操作顶多使用3次。

我们对移动方法1,2,3进行枚举,每种方法无非实施0-3次,也即一共4^3=64种情况。这些情况之间并非没有关系。例如,我们确定了1,2,3的情况数,那么得到一个灯A,B,C的状态,而只有移动4能够改变A,移动5能够改变B,移动6能够改变C,那么移动4-6的次数也确定了。同样,这时只有移动7能够改变D,移动9能够改变F,这时移动7和9的次数也确定了。最后,时钟A,B,C,D,F都已经到达12点,E,G,H,I还没确定,只剩下移动8能够改变GHI,所以只要检查E是否已经到达12点以及,GHI的时钟数是否相等就行了。最后找到一个移动次数最小的情况。

这题也可以用暴力搜索,因为最多有4^9个组合,不会超时。

这题还可以列出一个方程组,九个未知数,通过高斯消元法来解方程组。


代码:

#include <iostream>
#include <vector>
#include <cstring>
using namespace std;


int initial_clocks[10]={0}, adjusted_clocks[10]={0}, op[10]={0};
vector<int> perfect_adjust;
const int operations[10][10] =
{
    {0},
    //  A  B  C  D  E  F  G  H  I
    {0, 1, 1, 0, 1, 1, 0, 0, 0, 0},  //op1: ABDE
    {0, 1, 1, 1, 0, 0, 0, 0, 0, 0},  //op2: ABC
    {0, 0, 1, 1, 0, 1, 1, 0, 0, 0},  //op3: BCEF
    {0, 1, 0, 0, 1, 0, 0, 1, 0, 0},  //op4: ADG
    {0, 0, 1, 0, 1, 1, 1, 0, 1, 0},  //op5: BDEFH
    {0, 0, 0, 1, 0, 0, 1, 0, 0, 1},  //op6: CFI
    {0, 0, 0, 0, 1, 1, 0, 1, 1, 0},  //op7: DEGH
    {0, 0, 0, 0, 0, 0, 0, 1, 1, 1},  //op8: GHI
    {0, 0, 0, 0, 0, 1, 1, 0, 1, 1}  //op9: EFHI
};
/*
调整时钟:
参数:
op_num:调整方式(9种)
op_counts:操作次数
*/
void operate(int op_num, int op_counts) {
    for(int k=1; k<=9; k++) {
        adjusted_clocks[k] += operations[op_num][k]*op_counts;
        adjusted_clocks[k] %= 4;
    }
}


int guess() {
    op[4]=(4-adjusted_clocks[1])%4;
    operate(4, op[4]);
    op[5]=(4-adjusted_clocks[2])%4;
    operate(5, op[5]);
    op[6]=(4-adjusted_clocks[3])%4;
    operate(6, op[6]);
    op[7]=(4-adjusted_clocks[4])%4;
    operate(7, op[7]);
    op[8]=(4-adjusted_clocks[7])%4;
    operate(8, op[8]);
    if (adjusted_clocks[5]==adjusted_clocks[6] && adjusted_clocks[5]==adjusted_clocks[8] && adjusted_clocks[5]==adjusted_clocks[9]){
        op[9] = (4-adjusted_clocks[9])%4;
        operate(9, op[9]);
        int steps=0;
        for (int k=1; k<=9; k++) {
            steps += op[k];
        }
        return steps;
    }
    return 101;
}


int enumerate() {
    int c=0;
    int minsteps=101;
    int steps=0;
    while(c<64) {
        op[3]=c/16;
        op[2]=c%16/4;
        op[1]=c%4;  
        memcpy(adjusted_clocks, initial_clocks, sizeof(initial_clocks));
        operate(3, op[3]);
        operate(2, op[2]);
        operate(1, op[1]);
        steps = guess();
        if (steps<minsteps) {
            minsteps = steps;
            perfect_adjust.clear();
            for(int k=1; k<=9; k++){
                while(op[k]--) {
                    perfect_adjust.push_back(k);
                }
            }
        }
        c++;
    }
    return minsteps;
}


int main() {


    for (int k=1; k<10; k++){
        cin >> initial_clocks[k];
    }
    if (enumerate()<101){
        for (int k=0; k<perfect_adjust.size(); k++){
            cout << perfect_adjust.at(k) << ' ';
        }
    }
    return 0;
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值