Topcoder SRM 543 DIV2 1000 EllysThreeRivers & DIV1 500 EllysRivers


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<= length ),则所要花费的时间为:

Δt=X2+width[i]2swim[i]+lengthXwalk
大概可以从这个直观的式子中看出些什么,譬如说一些枚举或者搜索?反正如果一条河可以这样求出来,那三条河也同样可以求。写一个三元的搜索,或者是一个 递归函数就可以了。

具体分析上面那个式子,左侧在不断增加的时候,右侧又在不断递减,可以预料到一般情况下,这个式子跟二次函数一样具有一个最低点,即最小值,而此时的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= 109 ,规定误差 < <script type="math/tex" id="MathJax-Element-3435"><</script>EPS就可以退出循环。
然后简单的数论做法用到了偏导数,看不懂跳过。


DIV1 500:
Task
其他描述不变,但是小C选择乘船,只有在每个整数点才有船港,并且只能从这边的船港到另一边的船港。同时数据范围分别扩增为: length <=10^5, N (小岛个数)<=50,walk,width[i],swim[i]<=10^6。


Solution
虽然Div2加强了数据范围,但还是将原来难转移的double pos变成了int pos,可以说算是有利有弊。做法不一样但是思路仍然可以借鉴。
如果我们采用Dijkstra算法(会whatever_limit_exceed)或者dp,结合三分查找可以达到 O(Nlengthlog2length) 的复杂度。(这里或许还要再分析一下为什么是 O(Nlengthlog2length) :首先如果采用动态规划求解,最暴力的做法是对于每条河,枚举西侧的船港与东侧的船港。此时需要 O(Nlength2) 的复杂度,而最后 O(length) 的目的是为了找最小值,那么上述三分枚举就可以缩小到近似 O(log2length) 。)

既然已经优化到 O(log2length) 了,我们可以尝试优化到 O(1) :对于西岸的两个相邻的船港 pos pos1 ,分别行驶到东岸的 to(pos) to(pos1) 。有一个直观的结论:

point(pos)>=point(pos1)
因为它满足 单调性,具体的证明可以通过YY。有了这个结论,我们可以从 length 到0反序枚举船舱,用point指向另一边的船舱然后滑动,这样p平摊下来还是 O(length) 的复杂度。于是此题得解。

#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);
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值