Task
有四座并排放置,宽度忽略不计,长度均为
length
(1<=
length
<=1000)的小岛,分别记为0~3;小岛之间都隔着一条河,分别记为0~2。小C在陆地上行走的速度为
walk
(1<=
walk
<=100),对于河i,知道它的宽度
width[i]
(1<=
width[i]
<=1000)以及小C在该河流中的游速
swim[i]
(1<=
swim[i]
<=100)(忽略河流游速)。
求小C从0号小岛最南端到3号小岛最北端的距离。
Solution
本题难在如何运用所推公式。先总结一下3条结论:
1)
在岸上走路的时间几乎没有。我们唯一需要行走的时间或许只是当
walk>swim[i]
的时候,因为如果
walk<swim[i]
,那么可以在河中平行着河岸游,但显然平行河岸游反而会浪费时间。然而根据样例,也仍然出现了根本不需要在陆地行走的情况。
2)
所有的陆地行走路程可以归到最后一个小岛上。
3)
小C不一定只向东游,最优解应该是向东北游。现在我们只考虑两座岛的情况,那么假设小C向北方向的位移为
X
(0<=
具体分析上面那个式子,左侧在不断增加的时候,右侧又在不断递减,可以预料到一般情况下,这个式子跟二次函数一样具有一个最低点,即最小值,而此时的X也处于临界处。采用二分枚举么,但它的01性并不是线性。所以我们采用三分枚举或者以斜率作为01性的二分枚举。
用三分枚举的时间复杂度应该是
O(loglength3)
,但或许并不是这样:
class EllysThreeRivers {
public:
int Length,Walk,Wid[3],Swim[3];
double rec(int c,double dis){
if(c==3)return (1.0*Length-dis)/(1.0*Walk);
double L=dis,R=1.0*Length,res=0,ans=0;
for(int p=0;p<100;p++){
double Lmid=L+(R-L)/3.0,Rmid=R-(R-L)/3.0;
double ansL=sqrt((Lmid-dis)*(Lmid-dis)+Wid[c]*Wid[c])/(1.0*Swim[c])+rec(c+1,Lmid);
double ansR=sqrt((Rmid-dis)*(Rmid-dis)+Wid[c]*Wid[c])/(1.0*Swim[c])+rec(c+1,Rmid);
if(ansL>ansR)res=Rmid,ans=ansR,L=Lmid;
else res=Lmid,ans=ansL,R=Rmid;
}
return ans;
}
double getMin(int length,int walk,vector<int> width,vector<int> swim) {
Length=length,Walk=walk;
for(int i=0;i<3;i++)
Wid[i]=width[i],Swim[i]=swim[i];
return rec(0,0.0);
}
};
上面代码中的二分跟通常的不一样,枚举了二分的次数。
原因很显然:因为我们二分的值是浮点数,是可能会出现一直在不断二分而L和R始终不会相同的情况,导致陷入死循环。解决方案有两个:
1)
在估算精度适宜的情况下,强制让其二分p次;
2)
如题解所说,设置一个极小的常数EPS=
10−9
,规定误差
<
<script type="math/tex" id="MathJax-Element-3435"><</script>EPS就可以退出循环。
然后简单的数论做法用到了偏导数,看不懂跳过。
DIV1 500:
Task
其他描述不变,但是小C选择乘船,只有在每个整数点才有船港,并且只能从这边的船港到另一边的船港。同时数据范围分别扩增为:
length
<=10^5,
N
(小岛个数)<=50,
Solution
虽然Div2加强了数据范围,但还是将原来难转移的double pos变成了int pos,可以说算是有利有弊。做法不一样但是思路仍然可以借鉴。
如果我们采用Dijkstra算法(会whatever_limit_exceed)或者dp,结合三分查找可以达到
O(N∗length∗log2length)
的复杂度。(这里或许还要再分析一下为什么是
O(N∗length∗log2length)
:首先如果采用动态规划求解,最暴力的做法是对于每条河,枚举西侧的船港与东侧的船港。此时需要
O(N∗length2)
的复杂度,而最后
O(length)
的目的是为了找最小值,那么上述三分枚举就可以缩小到近似
O(log2length)
。)
既然已经优化到 O(log2length) 了,我们可以尝试优化到 O(1) :对于西岸的两个相邻的船港 pos 与 pos−1 ,分别行驶到东岸的 to(pos) 与 to(pos−1) 。有一个直观的结论:
#define wid first
#define swi second
class EllysRivers {
public:
static const int M=100005;
pair<double,double>Data[55];
double dp[2][M];
double sqr(double x){return x*x;}
double eval(int N,int length,int walk){
int cur=0;
for(int i=0;i<=length;i++)
dp[cur][length-i]=i*1.0/walk;
for(int i=N-1;i>=0;--i){
int pos=length;cur^=1;
for(int j=length;j>=0;--j){
dp[cur][j]=dp[cur^1][pos]+sqrt(sqr(Data[i].wid)+sqr(1.0*pos-j))/Data[i].swi;
while(pos>j){//合计O(n)找最小值
double nxt=dp[cur^1][pos-1]+sqrt(sqr(Data[i].wid)+sqr(1.0*pos-1-j))/Data[i].swi;
if(nxt>dp[cur][j])break;
dp[cur][j]=nxt;pos--;
}
}
}
return dp[cur][0];
}
double getMin(int length,int walk,vector<int> width,vector<int> speed) {
int N=width.size();
for(int i=0;i<N;i++){
Data[i].wid=width[i];
Data[i].swi=speed[i];
}
return eval(N,length,walk);
}
};