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;
} */
}
}
}