51nod-方阵与完全平方数(构造矩阵+dfs)

题目来源: mostleg
基准时间限制:1 秒 空间限制:131072 KB 分值: 40  难度:4级算法题
 收藏
 关注
如果一个由正整数组成的n*n的方阵,满足以下条件:
1,每个数字各不相同
2,每行以及每列的和,都是互不相同的完全平方数

我们称这种方阵为超级完全平方数方阵。
输入n,输出一个n*n的超级完全平方数方阵。如果存在多个方阵满足条件,输出将所有元素按行、列顺序排列后字典序最小的一个答案。例如
n=3时,下面两个方阵都符合条件

 1   2   6
 3   4   9
21 30 49

21 30 49
 3   4   9
 1   2   6

按行、列顺序排列后,第一个方阵表示为[1, 2, 6, 3, 4, 9, 21, 30, 49],第二个方阵表示为[21, 30, 49, 3, 4, 9, 1, 2, 6]。第一个方阵字典序更小一些。

如果不存在这样的方阵,输出No Solution。

Input
仅一行,为一个正整数n。(1 <= n <= 64)
Output
输出n行,每行为n个整数,之间用空格隔开,表示所求的n*n方阵。或者,输出No Solution。
Input示例
3
Output示例
1 2 6
3 4 9
21 30 49
mostleg  (题目提供者)
题解:

首先,n=1时无解。

接下来处理n>=2的情况。由于 题目要求字典序最小的方阵,使用贪心算法的思想,不难发现,每一行每一列其实只需要依靠最后一个数字(最右边和最下边的数字)就足够使得该行该列的和达到一个没有使用过的完全平方数。因此,按照题目中对方阵序列化的次序,对无关紧要的位置都尽力使用最小的数字;每当到达一行的最后一个位置,或者最后一行的时候,再去寻找符合题目要求的最后一个数字。这样做直到右下角的最后一个位置。

此时,最后一行和最后一列都需要满足和为完全平方数的条件。搜索最小的符合条件的数字。如果找不到解,就加大倒数第二个位置的数字(因为这样做对字典序的影响最小),再重新搜索最后一个位置。

怎样快速发现最后一个位置找不到解呢?不难发现,最后一列的和必定小于最后一行的和,设它们的差为d。我们可以枚举较小的一个完全平方数x,如果发现x的下一个完全平方数与x的差已经大于d,则在最后一个位置无解。


#include<math.h>
#include<stdio.h>  
#include<string.h>  
#include<algorithm>
using namespace std;
typedef long long ll;
#define maxn 65
ll a[maxn][maxn],n,flag,used[100005],used1[100005];
ll find(ll x)
{
	ll i;
	for(i=2;;i++)
		if(i*i>=x && (i<10000 && used[i]==0 || i>10000))
			return i;
}
void dfs(ll x,ll y,ll id)
{
	if(flag)
		return;
	if(x==y && x==n)
	{
		ll i,j,h,k,xx=0,yy=0;
		for(i=1;i<=n-1;i++)
		{
			xx+=a[i][y];
			yy+=a[x][i];
		}                         
	   for(i=2;;i++)//枚举第n行的和
	   {
		   if(i<10000 && used[i])
			   continue;
		   double tmp=sqrt((double)(i*i-xx+yy));
		   if(i*i-xx>0 && tmp==(ll)tmp && (tmp<10000 && used[(ll)tmp]==0|| tmp>10000))
		   {
			   a[x][y]=i*i-xx;
			   for(h=1;h<=n;h++)
			   {
				   for(k=1;k<=n;k++)
					   printf("%lld ",a[h][k]);
				   printf("\n");
			   }
			   flag=1;
			   return;
		   }
		   long long t=yy-xx;
		   if((i+1)*(i+1)-i*i>t)
		   {
			   long long sum=0;
			   for(k=1;k<=n;k++)
				   sum+=a[k][y-1];
			   used[(ll)sqrt((double)sum)]=0;
			   dfs(x,y-1,id+1);
			   return;
		   }
	   }
	}
	else if(x==n)
	{
		ll i,xx=0;
		for(i=1;i<=n-1;i++)
			xx+=a[i][y];
		i=find(xx+id);
		while(used1[i*i-xx]==1 || used[i]==1)
			i++;
		a[x][y]=i*i-xx;
		used1[i*i-xx]=1;
		used[i]=1;
		dfs(x,y+1,id);
	}
	else if(y==n)
	{
		ll i,xx=0;
		for(i=1;i<=n-1;i++)
			xx+=a[x][i];
		i=find(xx+id);
		while(used1[i*i-xx]==1)
			i++;
		a[x][y]=i*i-xx;
		used1[i*i-xx]=1;
		used[i]=1;
		dfs(x+1,1,id);
	}
	else
	{
		a[x][y]=id;
		used1[id]=1;
		if(used1[id+1]==0)
			dfs(x,y+1,id+1);
		else
		{
			while(used1[id+1]==1)
				id++;
			dfs(x,y+1,id+1);
		}
	}
}
int main()
{
	scanf("%d",&n);
	if(n==1)
	{   
		printf("No Solution\n");
		return 0;
	}
	a[1][1]=1;
	dfs(1,1,1);
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值