bsoj 2684 锯木场选址(DP+斜率优化)

【模拟试题】锯木场选址

Time Limit:10000MS  Memory Limit:65536K
Total Submit:464 Accepted:153 
Case Time Limit:1000MS

Description

  从山顶上到山底下沿着一条直线种植了n棵老树。当地的政府决定把他们砍下来。为了不浪费任何一棵木材,树被砍倒后要运送到锯木厂。 
  木材只能按照一个方向运输:朝山下运。山脚下有一个锯木厂。另外两个锯木厂将新修建在山路上。你必须决定在哪里修建两个锯木厂,使得传输的费用总和最小。假定运输每公斤木材每米需要一分钱。 
  你的任务是编写一个程序,从输入文件中读入树的个数和他们的重量与位置,计算最小运输费用。 

Input

  输入的第一行为一个正整数n——树的个数(2≤n≤20000)。树从山顶到山脚按照1,2……n标号。接下来n行,每行有两个正整数(用空格分开)。第i+1行含有:wi——第i棵树的重量(公斤为单位)和 di——第i棵树和第i+1棵树之间的距离,1≤wi≤10000,0≤di≤10000。最后一个数dn,表示第n棵树到山脚的锯木厂的距离。保证所有树运到山脚的锯木厂所需要的费用小于2000000000分。

Output

  输出只有一行一个数:最小的运输费用。

Sample Input

  9
  1 2
  2 1
  3 3
  1 1
  3 2
  1 6
  2 1
  1 2
  1 1

Sample Output

  26

Source

xinyue

题目:http://mail.bashu.cn:8080/bs_oj/showproblem?problem_id=2684

数据下载:http://main.edu.pl/en/user.phtml?op=zasoby

PS:这题的题目地址不好找啊,巴蜀的地址换过一次。。。后来我写的代码一直wa,只好找数据了,原来是1L*a,并不能将 a 转成long long类型,这个错误不能再犯了

题意:给你n棵树,从山顶排到山脚,山脚下有个伐木场,你也可以在路上建两个伐木场,问将所有树砍下运到伐木场的最小费用,树只能往下运

分析:这题还是比较复杂的,一开始应该能想到枚举两个伐木场的位置,然后计算最优值,不过这样的复杂度 是O(n^2)的,肯定会超时 T_T

不过千万不能灰心,写出式子看看 

f[ i ] = min { s [ n+1 , j ] +s [ j-1 , i ] + s[ i-1 , 1] } 1<=i<=j<=n+1 

s[  l , r ] = w [ l ]*( d[ l ] - d[ l ]) + w[ l-1 ]* (d[ l-1 ]- d[ l ]) +.... +w[ r ] * ( d[ r ] -d [ l ])

设 sw[ i ] = sum{ w[ j ] } ( i<=j<=n )       ,   swd[ i  ]= sum{  w[ j ] * d[  j ]  } (i<=j<=n)   d[ j ]与题目不同,为 j 到山脚的距离

那么经过各种YY可以得出

f[ i ] =min {  - d [ j-1 ]* sw[ i ]+ d[ j-1 ]*sw[ j ] }+ sw[ 1 ]- d[ i-1 ]*(sw[ 1 ] -sw [ i ])

设 x= d[ j-1 ] ,  y=sw[  j ] *d[ j-1 ], a=sw[ i ]

由于后面的 sw[ 1 ]- d[ i-1 ]*(sw[ 1 ] -sw [ i ]) 只与i相关,所有不管,那么求f[ i ]转换成求函数 G=-a*x +  y的最小值

转换下即y=ax + G,找到一个点使得G最小,由于斜率是固定的,那么就等价于将一条斜率为a 的直线不断往上移,直到遇到一个点,这个点便是最小值,而且可以证明这个点在直线与下凸线的切线上,由于直线的斜率a=sw [ i ]是不断增长的,那么位于这个点之前的点都不能构成新的最值,所以可以舍弃。。。。


以上我推导了两遍,因为一直wa,以为是推导错误,结果居然是判断大小的时候会超int。。。而我用1L乘之,一直wa。。。

代码:

#include<cstdio>
#include<iostream>
using namespace std;
const int mm=22222;
int d[mm],w[mm],sw[mm],swd[mm],q[mm];
int i,j,l,r,n,ans;
bool TRight(int ax,int ay,int bx,int by,int cx,int cy)
{
    return 1.0*(ax-bx)*(cy-by)>=1.0*(ay-by)*(cx-bx);
}
int gx(int i)
{
    return d[i-1];
}
int gy(int i)
{
    return sw[i]*d[i-1];
}
int get(int i,int a)
{
    return gy(i)-a*gx(i);
}
int main()
{
    while(~scanf("%d",&n))
    {
        for(i=1;i<=n;++i)
            scanf("%d%d",&w[i],&d[i]);
        for(i=n-1;i>0;--i)
            d[i]+=d[i+1];
        d[n+1]=sw[n+1]=swd[n+1]=0;
        for(i=n;i>0;--i)
        {
            sw[i]=sw[i+1]+w[i];
            swd[i]=swd[i+1]+w[i]*d[i];
        }
        ans=swd[1];
        l=0,r=-1;
        for(i=n;i>0;--i)
        {
            while(l<r&&TRight(gx(q[r-1]),gy(q[r-1]),gx(q[r]),gy(q[r]),gx(i+1),gy(i+1)))--r;
            q[++r]=i+1;
            while(l<r&&get(q[l],sw[i])>=get(q[l+1],sw[i]))++l;
            ans=min(ans,get(q[l],sw[i])-d[i-1]*(sw[1]-sw[i])+swd[1]);
        }
        printf("%d\n",ans);
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值