数字小游戏
最近三蹦子出了一个数字小游戏,游戏规则如图。玩了几局之后,突然兴起想写个程序来求解。没深究过算法,用的是最笨的枚举法。
游戏规则
如图
思路分析
按照我自己的手推过程,先找到3或者4填入,然后再找单个数字的行或列填空。但是按照计算机的处理逻辑,我们需要逐行(列)填入并逐列(行)分析,这里以列填入为例。整体思路就是回溯+剪枝,从第一列开始遍历该列每一种可行的填法,依次遍历所有列。每填一列都要对改变的行进行检查,看看是否合法。若都合法则填下一列,否则剪枝。只要最后结果的行数字与题目给定的相同,则结果正确。
算法解析
首先是数据结构。我们用4×4的二维数组来存储整个方框,0和1分别表示未填和已填。对于行列数字,由于数量不固定(1个或2个数字),这里我们采用vector容器来存储(当然也可以用长度为2的二维数组)。row存储行数字,col存储列数字。
int graph[4][4]={0};
vector<int>row[4],col[4];
接下来是列数字的填法,如下:
数字:1 填法:0,1,2,3
数字:2 填法:01,12,23
数字:3 填法:012,123
数字:4 填法:0123
数字:11 填法:02,13,03
数字:12 填法:013,023
填法的种类是很有限的,所有我们直接枚举就行。
这里将一个和两个数字分开进行枚举填空,check(i)表示检查此时第i行是否合法,合法则返回1。
if(col[column].size()==1){ //一个数
//数字是1;
if(col[column][0]==1){
for(int i=0;i<4;i++){
graph[i][column]=1;
if(check(i)){
seek(column+1);
}
graph[i][column]=0; //恢复现场
}
}
//数字是2
if(col[column][0]==2){
graph[0][column]=graph[1][column]=1; //填第0和1行
if(check(0)&&check(1)){
seek(column+1);
}
graph[0][column]=0; //恢复现场
graph[2][column]=1; //填第1和2行
if(check(1)&&check(2)){
seek(column+1);
}
graph[1][column]=0; //恢复现场
graph[3][column]=1; //填第2和3行
if(check(2)&&check(3)){
seek(column+1);
}
graph[2][column]=graph[3][column]=0; //恢复现场
}
//数字是3
if(col[column][0]==3){
graph[1][column]=graph[2][column]=1;
graph[0][column]=1; //填第0,1和2行
if(check(0)&&check(1)&&check(2)){
seek(column+1);
}
graph[0][column]=0; //恢复现场
graph[3][column]=1; //填第1,2和3行
if(check(1)&&check(2)&&check(3)){
seek(column+1);
}
graph[1][column]=graph[2][column]=graph[3][column]=0;//恢复现场
}
//数字是4
if(col[column][0]==4){
graph[0][column]=graph[1][column]=graph[2][column]=graph[3][column]=1;
//填每一行,这里不需要判断可以直接填下一列。
seek(column+1);
graph[1][column]=graph[2][column]=graph[3][column]=0;
}
}
else{ //有两个数
//数字是两个1
if(col[column][0]==col[column][1]){
graph[0][column]=graph[2][column]=1; //填第0和2行
if(check(0)&&check(2)){
seek(column+1);
}
graph[0][column]=graph[2][column]=0;
graph[1][column]=graph[3][column]=1; //填第1和3行
if(check(1)&&check(3)){
seek(column+1);
}
graph[1][column]=graph[3][column]=0;
graph[0][column]=graph[3][column]=1; //填第0和3行
if(check(0)&&check(3)){
seek(column+1);
}
graph[0][column]=graph[3][column]=0;
}
//数字是1和2
else{
graph[0][column]=graph[3][column]=1;
graph[2][column]=1; //填第0,2和3行
if(check(0)&&check(2)&&check(3)){
seek(column+1);
}
graph[2][column]=0;
graph[1][column]=1; //填第0,1和3行
if(check(0)&&check(1)&&check(3)){
seek(column+1);
}
graph[1][column]=0;
graph[0][column]=graph[3][column]=0;
}
}
最后是检查函数check。对于当前行我们先将已有的行数字提取出来,具体操作类似于提取句子中的单词。我们用容器p来存储某一行的行数字,如下:
vector<int>p;
int n=0;
for(int i=0;i<4;i++){
if(graph[r][i])
n++;
else if(n>0){
p.push_back(n);
n=0;
}
}
if(n>0){
p.push_back(n);
}
然后进行比较。如果第r行是合法的,那么p中数字的个数一定小于或等于row[r](举个反例,p里面是1和2,但是row[r]中只有2,那么往后再怎么填p和row[r]也不可能相同。),小于的话往后的列继续填空有机会使p和row[r]相同。具体算法如下(这里return 1表示合法,0表示非法。):
if(p.size()==row[r].size()){
if(p.size()==1&&p[0]<=row[r][0])
return 1;
if((p[0]<=row[r][0]&&p[1]<=row[r][1])||(p[0]<=row[r][1]&&p[1]<=row[r][0]))
return 1;
}
if(p.size()<row[r].size()&&(p[0]<=row[r][0]||p[0]<=row[r][1]))
return 1;
部分读者可能会有疑问,这里的比较用的都是小于等于,最后的结果一定是等于的吗?答案是肯定的。容易得到,行数字的和等于列数字的和。我们将行数字填入图中,它们的总和是不变的。如果图中每一行的行数字都小于等于题给的行数字,那么最后的结果只可能是都相等。
完成代码
先理一遍整体过程:
1、输入共8行,每行第一个数字n代表个数,后面跟着n个数字,先列数字后行数字;
2、将行列数字分别存入row和col容器中;
3、seek(int column)函数:若到达第四列,说明得到结果,可直接打印;否则继续走流程;
4、check(int r)函数:判断当前该行是否合法,是返回1,否返回0。
代码中的注释为调试使用,读者可自行删除。
#include <iostream>
#include <vector>
using namespace std;
int graph[4][4]={0};
vector<int>row[4],col[4];
int cnt,pos;
void print(){
for(int i=0;i<4;i++){
for(int j=0;j<4;j++)
cout<<graph[i][j]<<' ';
cout<<endl;
}
}
int check(int r){
//cout<<"row "<<r<<" :";
vector<int>p;
int n=0;
for(int i=0;i<4;i++){
if(graph[r][i])
n++;
else if(n>0){
p.push_back(n);
//cout<<n<<endl;
n=0;
}
}
if(n>0){
//cout<<n<<endl;
p.push_back(n);
}
if(p.size()==row[r].size()){
if(p.size()==1&&p[0]<=row[r][0])
return 1;
if((p[0]<=row[r][0]&&p[1]<=row[r][1])||(p[0]<=row[r][1]&&p[1]<=row[r][0]))
return 1;
}
if(p.size()<row[r].size()&&(p[0]<=row[r][0]||p[0]<=row[r][1]))
return 1;
return 0;
}
void seek(int column){ //fill the blank
if(column>=4){
cout<<"the answer is :"<<endl;
print();
return;
}
//cout<<"column "<<column<<" has "<<col[column].size()<<endl;
if(col[column].size()==1){ //just one number;
//the number is 1;
if(col[column][0]==1){
for(int i=0;i<4;i++){
graph[i][column]=1;
if(check(i)){
//cout<<"one YES in number "<<i<<endl;
//print();
seek(column+1);
}
graph[i][column]=0;
}
}
//the number is 2;
if(col[column][0]==2){
graph[0][column]=graph[1][column]=1; //row 0 and 1
if(check(0)&&check(1)){
//cout<<"the first two"<<endl;
//print();
seek(column+1);
}
graph[0][column]=0;
graph[2][column]=1; //row 1 and 2
if(check(1)&&check(2)){
//cout<<"the second two"<<endl;
//print();
seek(column+1);
}
graph[1][column]=0;
graph[3][column]=1; //row 2 and 3
if(check(2)&&check(3)){
//cout<<"the three two"<<endl;
//print();
seek(column+1);
}
graph[2][column]=graph[3][column]=0;
}
//the number is 3;
if(col[column][0]==3){
graph[0][column]=graph[1][column]=graph[2][column]=1; //row 0, 1 and 2
if(check(0)&&check(1)&&check(2)){
//cout<<"the first three"<<endl;
//print();
seek(column+1);
}
graph[0][column]=0;
graph[3][column]=1; //row 1, 2 and 3
//cout<<endl<<"the second three"<<endl<<"check(1): "<<endl<<check(1)<<endl<<"check(2): "<<endl<<check(2)<<endl<<"check(3): "<<endl<<check(3)<<endl;
if(check(1)&&check(2)&&check(3)){
//cout<<"the second three"<<endl;
//print();
seek(column+1);
}
graph[1][column]=graph[2][column]=graph[3][column]=0;
}
if(col[column][0]==4){
graph[0][column]=graph[1][column]=graph[2][column]=graph[3][column]=1; //all rows
//cout<<"all four"<<endl;
//print();
seek(column+1);
graph[1][column]=graph[2][column]=graph[3][column]=0;
}
}
else{ //two numbers
//1 and 1
if(col[column][0]==col[column][1]){
graph[0][column]=graph[2][column]=1; //row 0 and 2
if(check(0)&&check(2)){
//cout<<"one and three"<<endl;
//print();
seek(column+1);
}
graph[0][column]=graph[2][column]=0;
graph[1][column]=graph[3][column]=1; //row 1 and 3
if(check(1)&&check(3)){
//cout<<"two and four"<<endl;
//print();
seek(column+1);
}
graph[1][column]=graph[3][column]=0;
graph[0][column]=graph[3][column]=1; //row 0 and 3
if(check(0)&&check(3)){
//cout<<"two and four"<<endl;
//print();
seek(column+1);
}
graph[0][column]=graph[3][column]=0;
}
//1 and 2
else{
graph[0][column]=graph[3][column]=1;
graph[2][column]=1; //row 0, 2 and 3
if(check(0)&&check(2)&&check(3)){
//cout<<"one, three and four"<<endl;
//print();
seek(column+1);
}
graph[2][column]=0;
graph[1][column]=1; //row 0, 1 and 3
if(check(0)&&check(1)&&check(3)){
//cout<<"one, two and four"<<endl;
//print();
seek(column+1);
}
graph[1][column]=0;
graph[0][column]=graph[3][column]=0;
}
}
}
int main(){
/*
col[0].push_back(3);
col[1].push_back(1);
col[2].push_back(3);
col[3].push_back(1);
col[3].push_back(1);
row[0].push_back(2);
row[0].push_back(1);
row[1].push_back(1);
row[1].push_back(1);
row[2].push_back(1);
row[2].push_back(1);
row[3].push_back(2);
*/
for(int i=0;i<4;i++){
cin>>cnt;
for(int j=0;j<cnt;j++){
cin>>pos;
col[i].push_back(pos);
}
}
for(int i=0;i<4;i++){
cin>>cnt;
for(int j=0;j<cnt;j++){
cin>>pos;
row[i].push_back(pos);
}
}
seek(0);
return 0;
}
以下是对图片中的例子进行求解的结果:
写在后面
已经很久没写过程序了,有点手生。本来也只是觉得这个游戏很适合用做编程题。当然不会有读者拿这个去过游戏吧,毕竟输入数据的时间,手推都得到结果了哈哈哈!如果读者有其他不同的解法,欢迎评论区谈论。