〖题目〗
树的重量
【问题描述】
树可以用来表示物种之间的进化关系。一棵"进化树"是一个带边权的树,其叶节点表示一个物种,两个叶节点之间的距离表示两个物种的差异。现在,一个重要的问题是,根据物种之间的距离,重构相应的"进化树"。
令N={1..n},用一个N上的矩阵M来定义树T。其中,矩阵M满足:对于任意的i,j,k,有M[i, j] + M[j, k]<=M[i, k]。树T满足:
1、叶节点属于集合N;
2、边权均为非负整数;
3、dT( i, j )=M[ i, j ] ,其中dT(i, j)表示树上i到j的最短路径长度。
如下图,矩阵M描述了一棵树。
树的重量是指树上的所有边权之和。对于任意给出的合法矩阵M,它所能表示树的重量是惟一确定的,不可能找到的两棵不同重量的树,它们都符合矩阵M。你的任务就是,根据给出的矩阵M,计算M所表示树的重量。下图是上面给出的矩阵M所能表示的一棵树,这棵树的总重量为15。
【输入格式】weight.in
输入数据包含若干组数据。每组数据的第一行是一个整数n(2<n<30)。其中n-1行,给出的是矩阵M的一个上三角(不包含对角线),矩阵中所有元素是不超过100的非负整数。输入数据保证合法。
输入数据以n=0结尾。
【输出格式】weight.out
对于每组输入,输出一行,一个整数,表示树的重量。
【输入样例】
5
5 9 12 8
8 11 7
5 1
4
4
15 36 60
31 55
36
0
【输出样例】
15
71
【限制】 内存:50 M 时间:1 S
【评测方式】
【数据规模】
〖分析〗
首先,先证明一个定理:已知4个点Q1,Q2,Q3,K,并且Q1、Q2、Q3之间的路径经过Q0,而且知道它们之间的路径的长度(在树中,任意两点间有且仅有一条路径),且则可以求出Q1、Q2、Q3到K点的长度。
证明:
设Q1—K—Q2的路径长度为M12,
Q1—K—Q3的路径长度为M13,
Q2—K—Q3的路径长度为M23,
Q1—K的路径长度为D1,
Q2—K的路径长度为D2,
Q3—K的路径长度为D3,
则
由“在树中,任意两点间有且仅有一条路径”得:
D2+D3=M23①
∵Q1—K—Q2与Q1—K—Q3有相同路径Q1—K,
∴D2=M12-D1,
D3=M13-D1,
∴D2-D3 =M12-M13②
∴由①②得D2=(M23+M12-M13)/2
同理,可以计算出D1,D3的值,
∴原定理得证。
既然证明了这个命题,问题就迎刃而解了。先建立一个点K,连接K与其他点之间的连线。然后利用上面这个定理可以求出每个点到K点的最小权。接下来将原最短路径表M中的Mij自减去点i到K的最小权和点j到K的最小权,并且将calc自加所有点到K的最小权。这样构成了一个新的最短路径表M2。
利用表M2将K进行拓展(把一个点拓展为一棵树),重复上面过程直到K不能拓展或只能拓展成一条边为止。最后calc自加上这条边的权值或0。则calc就是最后的答案。
本来以为这样就可以通过了。结果只过了一半的点。为什么?就是因为没有对M2进行处理。如果两个点的最短路径为0,就意味着这两个点重合了。这个问题将会使M2无法进行进一步的改进,自然就会产生错误。
其实解决这个问题还是比较简单的,用染色法。若两个点的最短路径为0,则染同一种颜色。当然,在染色之前需要进行一次传递闭包,这样可以减少很多的计算量。
染完色,就是处理。建立新的表M3,其中第i行第j列的值就是上了颜色i与上了颜色j的点之间的路径(这条路径是唯一的)。
用M3代替M2,进行计算。
不过这样还不能全对。因为还有一组BT数据:
4
2 13 13
15 15
0
这意味着刚开始就有两个点重复。所以应该是先染一次色。
到这里,就AC了。
〖程序〗
// TASK: weight
var
m,mm:array [0..30,0..30] of longint;
c:array [0..30] of longint;
n:longint;
f:text;
d:array [0..30] of longint;
function max(a,b:longint):longint;
begin
if a>b then exit(a)
else exit(b);
end;
function min(a,b:longint):longint;
begin
if a<b then exit(a)
else exit(b);
end;
function init:boolean;
var
i,j,k:longint;
begin
readln(f,n);
if n<=0 then exit(false);
fillchar(m,sizeof(m),0);
for i:=1 to n-1 do
for j:=i+1 to n do
begin
read(f,m[j,i]);
m[i,j]:=m[j,i];
end;
exit(true);
end;
procedure draw;
var
i,j,k,ii,jj,kk,ln:longint;
ls:longint;
begin
fillchar(c,sizeof(c),0);
ln:=0;
for i:=1 to n do
if c[i]<=0 then
begin
inc(ln);
c[i]:=ln;
for j:=1 to n do
if m[i,j]<=0 then
c[j]:=ln;
end;
mm:=m;fillchar(m,sizeof(m),0);
for i:=1 to ln-1 do
begin
for ii:=1 to n do
if c[ii]=i then
begin
for j:=i+1 to ln do
for jj:=1 to n do
if c[jj]=j then
begin
m[i,j]:=mm[ii,jj];
m[j,i]:=mm[ii,jj];break;
end;
if m[i,j]>0 then break;
end;
end;
n:=ln;
end;
function solve:longint;
var
i,j,k,ii,jj,ln:longint;
ls:longint;
sum:longint;
begin
sum:=0;
for i:=1 to n do
d[i]:=maxint shl 10;
for k:=1 to n do
for i:=1 to n do
if m[i,k]>0 then
for j:=1 to n do
if (m[i,j]>0)and(m[j,k]>0) then
d[i]:=min(d[i],(m[i,j]+m[i,k]-m[j,k])div 2);
for i:=1 to n do
if d[i]>=maxint then
d[i]:=0;
for i:=1 to n do
inc(sum,d[i]);
for i:=1 to n do
for j:=1 to n do
if i<>j then
dec(m[i,j],d[i]+d[j]);
exit(sum)
end;
function calc:longint;
var
i,j,k,ii,jj,ln:longint;
ls:longint;
begin
calc:=0;draw;
while true do
begin
k:=solve;
if k<=0 then break;
inc(calc,k);
draw;
end;
for i:=1 to n do
for j:=1 to n do
if m[i,j]>0 then
begin
inc(calc,m[i,j]);
exit;
end;
end;
begin
assign(f,'weight.in');reset(f);
assign(output,'weight.out');rewrite(output);
while init do
writeln(calc);
close(f);close(output);
end.