HDU 2515 从任意两数和的数表倒推 代码的实现*

分析(来自黑书):
为了研究方便,设这n个整数从小到大依次为A1,A2,A3,…,也将n(n-1)/2个和数从小到大依次设为K1,K2,K3…
从边缘数据入手,根据简单的大小比较,很容易发现A1+A2=K1,A1+K3=K2。因为无法直接确定A2+A3的大小
(因为K3有可能等于A1+A4或A2+A3),所以只能假设A2+A3 = Kx,然后通过解方程求出A1,A2,A3的值。知道A1,
A2,A3以后,接下来的工作就是依次递推出每个数。假设已经求出前w个数的值,并将相应的w(w-1)/2个和数从数
列{Kn}中去除,那么考虑剩下的和数中最小的那一个Ki。为使得Ki最小,Ki必然是A1Aw中的最小数与Aw+1An
中的最小数相加而得,即Ki=A1+Aw+1。由于Ki,A1已知,因此Aw+1也确定了。
下面举个例子。
K={4,5,7,10,12,13,13,14,19},因为A1+A2总是最小的,A1+A3是第二小的。所以:
A1+A2=4(K1)
A1+A3=5(K2)
我们不知道A2+A3的值,但是由于只有A1+Ax(1<=x<=n)可能比它小,因此它在K中的位置应该为3~(n+1),
。我们枚举每种情况,
假设A2+A3=K3=7得到方程组:
A1+A2=4(K1)
A1+A3=5(K2)
A2 +A3=7 (K3 )
它有整数解A1=1,A2=3,A3=4。把这三个数两两之和K1,K2,K3去掉,则现在K={10,11,12,13,13,14,19}。这三个数中最小的是10,显然它等于A1+A4,因此A4=10-A1=9。把A4产生的和A1+A4,A2+A4,A3+A4去掉,得:
K={11,13,14,19}。其中最小数11应该等于A1+A5,因此A5=11-A1=10。
假设A2+A3=K4=10,方程组没有整数解;
假设A2+A3=K5=11,方程组没有整正整数解。
因此本题的唯一解是A={1,3,4,9,10}。枚举x的值(A2+A3=Kx)需要O(n)次,每次枚举需要递推O(n)个
————————————————
版权声明:本文为CSDN博主「Raise」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/linraise/article/details/17243567

一开始写的时候没有检查在选出来的那个最小的和数所对应的A3的解的情况下,每次求出一个数,该数是否和前面的数相加都符合要求。导致错了。实际上根据分析,当选出了w个数,就要从k数组中删去所有这w个数的任意两个数的和。必定是要检查的。然后又写错了一个数,死活检查不出来,浪费了好多时间。
对于这种繁琐,琐碎的代码自己还有很大的提升空间,需要先把框架搭好,想清楚每步应该怎么做来节省时间。

#include <iostream>
#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
int n, t;
int ans_num[50 + 1];
int k[1225+1];
int vis[1225 + 1];
int A1;
bool isRebuild() //判断是否可以重建数表
{
	//printf("111\n");
	int j = 3, i, q, p;
	for (i = 4; i <= n; ++i)
	{
		//printf("最初j=%d\n", j);
		while (j <= t && vis[j]==1)
			j++;
		//printf("最小和数下标j=%d\n", j);
		if (j > t)//所有都已经算出来,返回真
			return true;

		vis[j] = true;
		ans_num[i] = k[j] - A1; // 找未被访问过的第一个值
		//printf("ans_num[%d]=%d\n", i, ans_num[i]);

        //检查
		for (q = 2; q < i; ++q)
		{
			for (p = j + 1; p <= t; ++p)
			{
				if (vis[p]==0 && ans_num[q] + ans_num[i] == k[p])
				{
					//printf("ans_num[%d]+ans_num[%d]=%d\n", q, i, k[p]);
					vis[p] = 1;
					//printf("vis[%d]=%d\n", p, vis[p]);
					break;
				}
			}
			if (p > t) //不符合要求
				return false;
		}
		//printf("符合要求\n");
	}
	return true;
}

int main()
{
	while (scanf("%d", &n) != EOF)
	{
		t = n * (n - 1) / 2;
		for (int i = 1; i <= t; i++)
		{
			scanf("%d", k + i);
		}
		sort(k + 1, k + t + 1);
		//printf("111\n");
		/*
		printf("排序后的数组为\n");
		for (int i = 1; i <= t; i++)
		{
			printf("%d ", k[i]);
		}
		printf("\n");
		*/
		for (int x = 3; x <= n; x++)               //A2+A3=Kx,而只有A1+At的形式有可能比A2+A3小,所以A2+A3最大的时候排在第n位,所以枚举到第n位
		{
			//printf("x=%d\n", x);
			//int vis_num = 3;
			double temp = (k[1] + k[2] - k[x]) / 2;
			A1 = (int)temp;
			if ((temp - (double)A1) == 0)
			{
				//printf("x=%d\n",x);
				ans_num[1] = A1;
				ans_num[2] = k[1] - A1;
				ans_num[3] = k[2] - A1;
				memset(vis, 0, sizeof(vis));
				vis[x] = 1;
				if (isRebuild())
				{
					//printf("111\n");
					for (int i = 1; i <= n; i++)
					{
						printf("%d\n", ans_num[i]);
					}
					break;
				}
			}
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值