首先,纪念我用Linux系统AC的第一题…
安装这个万恶的NOI Linux系统费了6小时的时间,不过好在最后终于装上了,但是因为我安装的Linux系统比较烂,还遭到了小花儿和js的鄙视,唉,本人蒟蒻,有什么办法呢…和SJZEZ的模拟赛Day2杯具,少考虑了一个条件丢掉100分,总分368,Rank3,被小花儿和SJZEZ的LYD神犇分别以480分和435分虐了,Day1还被SXY神犇虐了7分,还好Day2追回来了,总分比SXY神犇多27分险居Rank3;CL2姐姐同杯具,Rank5。非常奇葩的是,两校排名冠亚军是男生,第三名到第五名被两校仅有的三个参加考试的女生占据…嗯,也算给NOIP涨了信心了吧。NOIP的目标很明确:拿省一、并尽量拿高分为省选做准备。
说说这道题吧:01分数规划的Dinkelbach迭代法+最小生成树的prim算法
题意:给出几个村庄的坐标x[i]和y[i],以及海拔z[i]。要在这些村庄之间建水渠,费用和两个村庄的海拔差成正比,水渠长度和村庄二维坐标(x,y)上的距离成正比,要求一种方案使得(总的花费/总的水渠长度)最小,输出这个最小值,保留三位小数。
算法讨论:经典的01分数规划。采用Dinkelbach迭代法比较快。01分数规划请参考本博:http://www.cnblogs.com/Thispoet/archive/2011/09/19/2181377.html
由于这是一个稠密图,采用prim算法较好。
迭代答案ans,定义a[i]=c[i]-d[i]*ans(相当于平常prim算法的lowcost数组),c[i]为a[i]取到最小值的时候i这个点到达集合的距离,d[i]为a[i]取到最小值时i这个点到达集合所需要的费用,每次取a[i]最小的加入集合当中,并利用它来更新其他点。定义temp=sigma(c[i])/sigma(d[i]),最后当ans和temp的差值达到精度要求的时候输出,否则令ans=temp继续上一步,直到满足要求为止。
代码写得不长,而且很顺利,也许是刚装上Linux激动的状态很好吧…
program poj2728;//By_Thispoet const maxn=1005; var i,j,n,p,pos :longint; x,y,z :array[0..maxn]of longint; a,d,c :array[0..maxn]of extended; ans,temp,t,k,m,q :extended; flag :array[0..maxn]of boolean; begin readln(n); while not (n=0) do begin for i:=1 to n do readln(x[i],y[i],z[i]); ans:=0;temp:=0; repeat fillchar(flag,sizeof(flag),0); flag[1]:=true;ans:=temp; for i:=2 to n do begin c[i]:=abs(z[i]-z[1]); d[i]:=sqrt(sqr(x[i]-x[1])+sqr(y[i]-y[1])); a[i]:=c[i]-d[i]*ans; end; p:=1;m:=0;q:=0; while p<n do begin temp:=maxlongint; for i:=2 to n do if (not flag[i])and(a[i]<temp) then begin temp:=a[i];pos:=i; end; flag[pos]:=true;m:=m+c[pos];q:=q+d[pos]; for i:=2 to n do if not flag[i] then begin t:=abs(z[i]-z[pos]);k:=sqrt(sqr(x[i]-x[pos])+sqr(y[i]-y[pos])); if (t-k*ans)<a[i] then begin a[i]:=t-k*ans;c[i]:=t;d[i]:=k; end; end; inc(p); end; temp:=m/q; until abs(ans-temp)<0.000001; writeln(ans:0:3);readln(n); end; end.