问题描述
以4×4格子形状摆放16个时钟。所有时钟的时针都指向12点、3点、6点或者9点。现在要把所有时钟的时针指向12点。修正时钟时间的唯一方式是,通过控制10个开关,且每个开关连接到少则3个、多则5个时钟上。每按1次开关,与之连接的时钟就会顺时针转动3个小时。
开关与连接的时钟有以下对应的关系。
输入
第一行输入测试用例的个数C(C≤30)。各测试用例以整数形式输入16个时钟当前指向的时间,其数值为12、3、6、9之一。
输出
每个测试用例在第1行显示出1个整数。这个整数表示,为了使所有时钟指向12点而按开关的次数。
解题思路:
要直接解这道题会比较麻烦。如果利用问题的特性,加以适当简化,就可以用旁举搜索法解决。
首先要弄清,与示例输入输出说明不同,其实按开关的顺序并不重要。即使调换按两个开关的顺序,也不会给结果带来任何变化。因此,最终要计算的只是共按下了几次开关而已。
变换到此,也不能马上适用穷举搜索法解题。想要利用穷举搜索法,必须列出按开关的所有组合。可是按同一开关的次数又没有限制,所以这种组合变得无穷多。
对于这种问题,有个循环规律值得利用:时钟每过12个小时就会回到起点。利用这种循环规律,可把无限组合变换为有限组合。对于同一开关,按4次就会让时钟转动12个小时,也就等同于没有按下开关。由此得出一个结论:对任意一个开关,最多只会按下3次。那么就有,对一个开关按下的次数是0到3的整数,共有10个开关,所以组合的个数为 检索这些组合,不同实现方法的耗时会有所不同,但能够得出“可在限定时间内完成检索”的结论。从而也能判断出利用穷举搜索法即可顺利解题。
注意事项
- 没有答案时返回的是INF。
- 开关与时钟的对应关系保存到二维数组。
const int INF = 9999 ,SWITCHES = 10,CLOCKS = 16;
// link[i][j] = 'x',表示第i个开关与第j个时钟相连
char linked[SWITCHES][CLOCKS+1] = {
"xx..............",
"...x...x.x.x....",
"....x.....x...xx",
"x...xxxx........",
"......xxx.x.x...",
"x.x...........xx",
"...x..........xx",
"....xx.x......xx",
".xxxxx..........",
"...xxx...x...x.."
};
//判断所有时钟是否都指向12点
bool areAligned(const vector<int>&clocks);
//按下第swtch 个开关
void push(vector<int>&clocks,int swtch){
for(int cloc = 0;cloc<CLOCKS;++cloc){
if(linked[swtch][cloc] == 'x'){
clocks[cloc] += 3;
if(clocks[cloc] == 15)clocks[cloc] = 3;
}
}
}
// clocks 当前所由时钟的状态
//swtch :当前要按的开关的序号
//此时返回 “按下剩余开关使clocks 调整为12点的最少次数
//若不可能调节为12点,则返回大于INF的数值
int solve(vector<int>&clocks,int swtch){
if(swtch == SWITCHES) // 到了最后一个开关了
return areAligned(clocks)?0:INF;
//对当前开关尝试0~3次
int ret = INF;
for(int cnt = 0;cnt<4;++cnt){
ret = min(ret,cnt + solve(clocks,swtch+1));
push(clocks,swtch);//4次就相当于dfs中的回溯
}
// push 一共调用4次所以与原来状态一样
return ret;
}