vijos-p1014 2008.11.6
动态规划 vijos-p1014--很强悍的dp Has comments
背景 Background
欧几里德旅行商(Euclidean Traveling Salesman)问题也就是货郎担问题一直是困扰全世界数学家、计算机学家的著名问题。现有的算法都没有办法在确定型机器上在多项式时间内求出最优解,但是有办法在多项式时间内求出一个较优解。
为了简化问题,而且保证能在多项式时间内求出最优解,J.L.Bentley提出了一种叫做bitonic tour的哈密尔顿环游。它的要求是任意两点(a,b)之间的相互到达的代价dist(a,b)=dist(b,a)且任意两点之间可以相互到达,并且环游的路线只能是从最西端单向到最东端,再单项返回最西端,并且是一个哈密尔顿回路。
描述 Description
著名的NPC难题的简化版本
现在笛卡尔平面上有n(n<=1000)个点,每个点的坐标为(x,y)(-2^31<x,y<2^31,且为整数),任意两点之间相互到达的代价为这两点的欧几里德距离,现要你编程求出最短bitonictour。
输入格式 Input Format
第一行一个整数n
接下来n行,每行两个整数x,y,表示某个点的坐标。
输入中保证没有重复的两点,
保证最西端和最东端都只有一个点。
输出格式 Output Format
一行,即最短回路的长度,保留2位小数。
样例输入 Sample Input
7
06
10
23
54
61
75
82
样例输出 Sample Output
25.58
这个题就是赤裸裸的Bitonic Tour问题了。我的解决方案如下:
将一个人从最左端走到最右端,然后从最右端走到最左端等价成两个人同时从最左端不重复的走过中间的点并且到达最右端。问题的转化,模型的构建是多么的强悍!
我们不妨设这两个人为A和B,且总是假定走在前面的人是A。再设函数F(x1, x2)表示A走到x1的位置,B走到x2的位置,并且所有x1,x2之前的位置都被不重复的走过的最短距离之和。根据前面的假设我们可以知道 x1>=x2,且F(xn,xn)即为所得。
下面的过程就是如何推导出状态方程,我把它划分为三种情况:
1) 如果x1=x2,即A和B处在同一点,那么F(x1,x2) = F(x1, x1) = F(x1, x1 - 1) + dist(x1, x1 - 1)
2) 如果x1=x2+1,即B在A的紧邻的靠后一点,那么F(x1,x2) = F(x2, x') + dist(x1, x') (1<= x' <=x2)
{为什么要选择在x1=x2+1时为计算最优值得点呢?
因为,当x1=x2+1时,1---x1这些点,刚好被走完,所以在此时,需要找到一个在1—x1间,的最优值,故, for k:=1 to j-1 do
iff[i-1,k]+d[i,k]<min then min:=f[i-1,k]+d[i,k];
f[i,j]:=min;
}
3) 如果x1>x2+1,即B离A在后面一个距离的范围以上,那么F(x1, x2) = F(x1 - 1, x2) + dist(x1, x1 - 1)
其实这里不用考虑如果x1,x2走到中间同一个点,因为很容易得到那样不是最优的。
以上算法时间复杂度与空间复杂度均为O(n*n)
题解中的:
for i:=1 to n do
begin
for j:=1 to i-1 do
if j=1 then
f[i,j]:=f[i-1,1]+d[i-1,i]else
if j=i-1 then
begin
min:=1e24;
for k:=1 to j-1 do
if f[i-1,k]+d[i,k]<min then min:=f[i-1,k]+d[i,k];
f[i,j]:=min;
end else
f[i,j]:=f[i-1,j]+d[i-1,i];
end;
我改进后的:
for i:=1 to n do
begin
for j:=1 to i-1 do
if (j=i-1)and(j<>1)then
begin
min:=1e24;
for k:=1 to j-1 do
if f[i-1,k]+d[i,k]<min then min:=f[i-1,k]+d[i,k];
f[i,j]:=min;
end
else
f[i,j]:=f[i-1,j]+d[i-1,i];
end;
program:
program vijosp1014;
const maxn=1000;
var
x,y:array[1..maxn] of real;
f,dist:array[0..maxn,0..maxn] of real;
i,j,n,k:longint;
min:real;
procedure qsort(p,q:longint);
var
i,j:longint;
temp,mid:real;
begin
i:=p;j:=q;mid:=x[(i+j) div 2];
repeat
while x[i]>mid do inc(i);
while x[j]<mid do dec(j);
if i<=j
then
begin
temp:=x[i];
x[i]:=x[j];
x[j]:=temp;
temp:=y[i];
y[i]:=y[j];
y[j]:=temp;
inc(i);dec(j);
end;
until i>j;
if i<q then qsort(i,q);
if j>p then qsort(p,j);
end;
function d(x1,x2,y1,y2:real):real;
begin
d:=sqrt(sqr(x1-x2)+sqr(y1-y2));
end;
begin
readln(n);
for i:=1 to n do readln(x[i],y[i]);
qsort(1,n);
for i:=1 to n do
for j:=1 to n do
dist[i,j]:=d(x[i],x[j],y[i],y[j]);
for i:=1 to n do
for j:=1 to i-1 do
if (j=i-1)and(j<>1)
then
begin
min:=10000000000000;
for k:=1 to j-1 do
if f[i-1,k]+dist[k,i]<min
then min:=f[i-1,k]+dist[k,i];
f[i,j]:=min;
end
else f[i,j]:=f[i-1,j]+dist[i-1,i];
writeln(f[n,n-1]+dist[n-1,n]:0:2);{这里的结果也要记住}
分成两种情况:A在B前面,B在A前面。
end.