看了网上很多都是用dfs解决的,于是自己就写了一篇用高斯消元法的解决方法
问题描述
把 1 2 3 … 19 共19个整数排列成六角形状,如下:
要求每个直线上的数字之和必须相等。共有15条直线哦!
再给点线索吧!我们预先填好了2个数字,第一行的头两个数字是:15 13,参见下图,黄色一行为所求。
请你填写出中间一行的5个数字。数字间用空格分开。
这是一行用空格分开的整数,请通过浏览器提交答案,不要填写任何多余的内容(比如说明性的文字等)
解法
把15条直线看成是15条方程,每一个圆看成一个未知数,且未知数的系数为1即六角幻方对应的未知数:
x0 x1 x2
x3 x4 x5 x6
x7 x8 x9 x10 x11
x12 x13 x14 x15
x16 x17 x18
x0+x1+x2=38
x3+x4+x5+x6=38
x7+x8+x9+x10+x11=38
x12+x13+x14+x15=38
x16+x17+x18=38
…太多了,我就不写了
代码
/*六角幻方的各个数的下标
x0 x1 x2
x3 x4 x5 x6
x7 x8 x9 x10 x11
x12 x13 x14 x15
x16 x17 x18
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
using namespace std;
#define N 19
#define M 15
int am[M][N],bm[M],col[N],vi[N],ans[N],ans2[N];
int in[M][7]={
{0,1,2,-1}, //-1作为结尾标志
{3,4,5,6,-1},
{7,8,9,10,11,-1},
{12,13,14,15,-1},
{16,17,18,-1},
{0,3,7,-1},
{1,4,8,12,-1},
{2,5,9,13,16,-1},
{6,10,14,17,-1},
{11,15,18,-1},
{2,6,11,-1},
{1,5,10,15,-1},
{0,4,9,14,18,-1},
{3,8,13,17,-1},
{7,12,16,-1}
};
void init(){
memset(am,0,sizeof(am));
for(int i=0;i<M;i++)
{
for(int j=0;in[i][j]!=-1;j++)
{
am[i][in[i][j]]=1;
}
bm[i]=38;
/*由于1到19的累加和为109,通过对图中随便构成5条不相交的直线,
即 {0,1,2,-1},
{3,4,5,6,-1},
{7,8,9,10,11,-1},
{12,13,14,15,-1},
{16,17,18,-1},
作为5条直线,且由题意可知每条直线总和相等,
所以109/5=38,即每条直线必定和为38
*/
}
for(int i=0;i<N;i++)
col[i]=i;//因为高斯消元中有交换列的处理,所以要标志i列为那个未知数
}
void r_swap(int i,int j){
if(i==j) return ;
for(int k=0;k<N;k++)
{
swap(am[i][k],am[j][k]);
}
swap(bm[i],bm[j]);
}
void c_swap(int k){
int i;
for(i=k;i<N;i++)
{
if(am[k][i]!=0)
break;
}
if(k==i || i>=N) return ;
for(int j=0;j<M;j++)
{
swap(am[j][k],am[j][i]);
}
swap(col[k],col[i]);
}
int gcd(int a,int b){
return b==0?a:gcd(b,a%b);
}
void xiao_yuan(int k,int j){
int u=am[k][k],v=am[j][k];
if(u==0||v==0) return ;
int g=gcd(u,v);
for(int i=0;i<N;i++)//必须从0开始,因为这一行要乘以u/g,且从0开始的元素并不一定为0
{
am[j][i]=(u*am[j][i]-v*am[k][i])/g;
/*
原本为am[j][i]=am[j][i]-v/u*am[k][i]);
但是v/u可能存在精度误差,所以解决这个问题最好是在计算过程中得出的都是整数
将 am[j][i]=am[j][i]-v/u*am[k][i])两边乘以u,得
u*am[j][i]=u*am[j][i]-v*am[k][i]
由于将方程组中的某一行两边同时乘以一个数,并不会影响方程组的求解,所以可以令
am[j][i]=u*am[j][i]=u*am[j][i]-v*am[k][i],即 am[j][i]=u*am[j][i]-v*am[k][i]
由于 u*am[j][i]-v*am[k][i]可能结果很大,所以除以u和v的最大公约数
最终得到 am[j][i]=(u*am[j][i]-v*am[k][i])/g的形式
*/
}
bm[j]=(u*bm[j]-v*bm[k])/g;//此处跟 am[j][i]=(u*am[j][i]-v*am[k][i])/g同理
}
void show(){
/*
static int cnt=0;
printf("第%d次消元\n",cnt);
*/
printf("最终消元结果:\n");
for(int i=0;i<N;i++)
{
printf("%5d",col[i]);
}
printf(" bm\n");
for(int i=0;i<(N+1)*5;i++)
printf("%c",'-');
printf("\n");
for(int i=0;i<M;i++)
{
for(int j=0;j<N;j++)
printf("%5d",am[i][j]);
printf("%5d\n",bm[i]);
}
printf("\n\n");
}
void guass(){
for(int i=0;i<M;i++)
{
//show();
for(int j=i;j<M;j++)
{
if(am[j][i]!=0)
{
r_swap(i,j);//找到不为零的行就交换
break;
}
}
c_swap(i);//如果没有找到不为零的行,就找不为零的列,然后交换
for(int j=0;j<M;j++)
{
if(j!=i)
xiao_yuan(i,j);
}
}
}
void output(){
//static int cnt=1;
//printf("no.%d\n",cnt++);
if(ans2[0]!=15||ans2[1]!=13)//不满足题目要求的结果不显示
return ;
printf("%*c%2d %2d %2d%*c\n\n",4,' ',ans2[0],ans2[1],ans2[2],6,' ');
printf("%*c%2d %2d %2d %2d%*c\n\n",2,' ',ans2[3],ans2[4],ans2[5],ans2[6],3,' ');
printf("%2d %2d %2d %2d %2d\n\n",ans2[7],ans2[8],ans2[9],ans2[10],ans2[11]);
printf("%*c%2d %2d %2d %2d%*c\n\n",2,' ',ans2[12],ans2[13],ans2[14],ans2[15],3,' ');
printf("%*c%2d %2d %2d%*c\n\n\n",4,' ',ans2[16],ans2[17],ans2[18],6,' ');
//getchar();
}
void check(){
for(int i=11;i>=0;i--)
{
int t=0;
for(int j=12;j<N;j++)
{
t+=am[i][j]*ans[j];
}
t=(bm[i]-t)/am[i][i];
if(t<1||t>N)
return ;
for(int j=i+1;j<N;j++)
{
/*判断填进去的数字是否有重复,此处其实可以使用set树,
然后查找,即时间复杂度为log2(N),但是这里N=19,运算量不大,
所以不使用该方法,有兴趣的小伙伴可以自己做一做 */
if(t==ans[j])
return ;
}
ans[i]=t;
}
for(int i=0;i<N;i++)
{
ans2[col[i]]=ans[i];//将结果放回第i列对应的未知数内
}
output();
}
void f(int *ans,int k){
if(k<12)//由于最后求出的行最简形矩阵只有12行,所以另外7未知数要事先填进去
{
check();
return ;
}
for(int i=1;i<=N;i++)
{
bool flag=true;
for(int j=k+1;j<N;j++)
{
if(i==ans[j])
{
flag=false;
break;
}
}
if(flag)
{
ans[k]=i;
f(ans,k-1);
}
}
}
int main(){
init();
guass();
show();
f(ans,18);
return 0;
}