递推法_HDU2515_Yanghee的数表

Problem Description
Yanghee 是一个小学生。他的数学老师给全班同学布置了一道家庭作业,即根据 
一张不超过5000的n(n<50)个正整数组成的数表,两两相加得到n(n-1)/2个和,然后把它们排序。例如,如果数表含有四个数1,3,4,9,那么正确答案是4,5,7,10,12,13。Yanghee 做完作业以后和小伙伴们出去玩了一下午,回家以后发现老师给的数表不见了,可是他算出的答案还在。你能帮助Yanghee根据他的答案计算出原来的数表吗? 
 
分析(来自黑书):
为了研究方便,设这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必然是A1~Aw中的最小数与Aw+1~An
中的最小数相加而得,即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)个数,每递推一个数需要用O(n)的时间来去掉已经得到的和,故总体复杂度O(n^3).
Talk is cheap,Show me the code!
#include <iostream>
#include <algorithm>
using namespace std;

int Arr[100];						//Arr means the array
int Sum[2500];						//Sum means the Sum array
int sNum;							//Sum number
int aNum;							//Arr number
bool visited[2500];					//visited[i] means Sum[i] is visited

//to calc a1,a2,a3,before,of course sum1,sum2,sumk is certain,
//visited[1],visited[2],visited[k] is true(be visited)
//memset visited is false

bool isRebuild()
{
	int i, j = 3, v, p;
	for ( i = 4; i <= aNum; ++i )
	{
		while ( j <= sNum && visited[j] )
			++j;
		if( j > sNum )						//all visited
			return true;

		visited[j] = true;
		Arr[i] = Sum[j] - Arr[1];

		for( v = 2; v < i; ++v )
		{
			for ( p = j+1; p <= sNum; ++p )
			{
				if( !visited[p] && Arr[v] + Arr[i] == Sum[p] )
				{
					visited[p] = true;
					break;
				}
			}
			if( p > sNum )
				return false;
		}
	}
	return true;
}
int main()
{
	int i,j;
	while( cin >> aNum )
	{
		sNum = aNum * ( aNum - 1 ) >> 1;
		for( i = 1; i <= sNum; ++i )
			cin >> Sum[i];

		sort( Sum, Sum + sNum );

		for( i = 3; i <= sNum; ++i )
		{
			Arr[1] = ( Sum[1] + Sum[2] - Sum[i] ) >> 1;
			Arr[2] = Sum[1] - Arr[1];
			Arr[3] = Sum[2] - Arr[1];
			if( Arr[2] + Arr[3] != Sum[i] )
				continue;

			memset( visited, false,sizeof(visited) );
			visited[i] = true;

			if( isRebuild() )
			{
				for( j = 1; j <= aNum; ++j )
					cout << Arr[j] << endl;
				break;
			}
		}
	}
}
Another One HDU1270
#include<iostream>
using namespace std;

//visited[i]=true表示sum[i]已经被算过一遍,对于数据量小,
//这种办法省空间,也可以visited[sum[i]],这种数据量小且数据跨度大时费空间

bool visited[10010];
int vex[1000];
int sum[10010];
int vexNum,Num;

/*调用之前需要先算出vex[1],vex[2],vex[3]
	a1+a2=sum1
	a1+a3=sum2
	a2+a3=sumk,3<=k<=Num
visited[i]置为false
其中visited[1]=visited[2]=visited[k]=true*/

bool isRebuild() //判断是否可以重建数表
{
	int index = 3, i, k, p;
	for( i = 4; i<=vexNum; ++i )
	{
		while( index <= Num && visited[index] )//找未被算过的和数(sum),越过已算过的sum
			++index;
		if( index > Num )//所有都已经算出来,返回真
			return true;

		visited[index] = true;
		vex[i] = sum[index] - vex[1]; // 找未被访问过的第一个值

		//判断vex[i]+vex[k]==sum[p]是否成立,2<=k<i,index+1<=p<=Num,如果成立说明ok,否则出错
		for( k = 2; k < i; ++k )
		{
			for( p = index + 1; p <= Num; ++p )
			{
				if ( !visited[p] && vex[k] + vex[i] == sum[p] )
				{
					visited[p] = true;
					break;
				}
			}
			if( p > Num ) //不存vex[i]+vex[k] == sum[p],则说明有两个vex[i],vex[k]之和并不在sum中,出错!
				return false;
		}
	}
	return true;
}
int main()
{
	int i,j;
	while( cin >> vexNum && vexNum != 0 )
	{
		Num = vexNum*(vexNum-1) / 2;
		for(i=1; i<=Num; ++i)
			cin >> sum[i];
		//memset(visited,false,sizeof(bool)*(Num+1));
		//a1+a2=sum1
		//a1+a3=sum2
		//a2+a3=sumk,3<=k<=Num
		for(i=3; i<=Num; ++i)
		{
			vex[1]=(sum[1]+sum[2]-sum[i])/2;
			vex[2]=sum[1]-vex[1];
			vex[3]=sum[2]-vex[1];
			if(vex[2]+vex[3] != sum[i])
				continue;
			memset(visited,false,sizeof(visited));
			visited[i] = true;
			if(isRebuild())
			{
				for(j=1; j<vexNum; ++j)
					cout << vex[j] <<' ';
				cout << vex[j] << endl;
				break;
			}
		/*  else
			{
				cout << "Are you kidding me?" << endl;
			}											*/
		}
	}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Raise

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值