题解:luogu4360 锯木厂选址

题目描述

从山顶上到山底下沿着一条直线种植了nn棵老树。当地的政府决定把他们砍下来。为了不浪费任何一棵木材,树被砍倒后要运送到锯木厂。

木材只能朝山下运。山脚下有一个锯木厂。另外两个锯木厂将新修建在山路上。你必须决定在哪里修建这两个锯木厂,使得运输的费用总和最小。假定运输每公斤木材每米需要一分钱。

你的任务是编写一个程序,从输入文件中读入树的个数和他们的重量与位置,计算最小运输费用。

输入输出格式

输入格式:

 

输入的第一行为一个正整数nn——树的个数(2≤n≤20000)(2n20000)。树从山顶到山脚按照1,2,...,n1,2,...,n标号。

接下来nn行,每行有两个正整数(用空格分开)。

i+1i+1行含有:w_iwi——第ii棵树的重量(公斤为单位)和 d_idi——第ii棵树和第i+1i+1棵树之间的距离,1≤w_i≤10000,0≤d_i≤100001wi10000,0di10000。

最后一颗树的d_ndn,表示第nn棵树到山脚的锯木厂的距离。保证所有树运到山脚的锯木厂所需要的费用小于2×10^92×109分。

 

输出格式:

 

输出最小的运输费用。

 

输入输出样例

输入样例#1:  复制
9 
1 2 
2 1 
3 3 
1 1 
3 2 
1 6 
2 1 
1 2 
1 1
输出样例#1:  复制

26

 

解题思路:

可以写出dp方程,发现满足斜率优化的特征,斜率优化

 

暴力dp(能ac)

 1#include<bits/stdc++.h>
2#define N 40000
3using namespace std;
4int w[N],sum[N],d[N],dis[N],n,cnt;
5int dp[N]; 
6void read(){
7    scanf("%d",&n);
8    for (int i=1;i<=n;i++)
9        scanf("%d%d",&w[i],&d[i]);
10    for (int i=1;i<=n;i++)
11        sum[i]=sum[i-1]+w[i];
12    for (int i=n;i>=1;i--)
13        dis[i]=dis[i+1]+d[i];
14    //for (int i=1;i<=n;i++)
15    //    cout << dis[i] << "  " << sum[i] << endl;
16}
17void solve(){
18    memset(dp,0x3f3f3f,sizeof(dp));
19    for (int i=1;i<=n;i++)
20        cnt+=w[i]*dis[i];
21    //cout << cnt <<"cnt"<< endl;
22    for (int i=1;i<=n;i++){
23        for (int j=1;j<i;j++)
24            dp[i]=min(dp[i],cnt-dis[i]*(sum[i]-sum[j])-dis[j]*sum[j]);
25    }
26    int ans=1023456789;
27    for (int i=1;i<=n;i++)
28        ans=min(ans,dp[i]); 
29    cout << ans <<endl;
30}
31int main(){
32    read();
33    solve();
34    return 0;
35}

斜率优化版本(比前一个快20倍):

 

 1#include<bits/stdc++.h>
2#define N 40000
3using namespace std;
4int w[N],sum[N],d[N],dis[N],n,cnt;
5int dp[N],q[N]; 
6double X(int x){
7    return sum[x]*dis[x];
8}
9double Y(int y){
10    return sum[y];
11}
12double slope(int i,int j){
13    return (X(i)-X(j))/(Y(i)-Y(j));
14}
15void read(){
16    scanf("%d",&n);
17    for (int i=1;i<=n;i++) scanf("%d%d",&w[i],&d[i]);
18    for (int i=1;i<=n;i++) sum[i]=sum[i-1]+w[i];
19    for (int i=n;i>=1;i--) dis[i]=dis[i+1]+d[i];
20    for (int i=1;i<=n;i++) cnt+=w[i]*dis[i];
21}
22void solve(){
23    memset(dp,0x3f3f3f,sizeof(dp));
24    int l=0,r=0;
25    for (int i=1;i<=n;i++){
26        while (l<r && slope(q[l],q[l+1])>dis[i])
27            l++;
28        int j=q[l];
29        dp[i]=cnt-dis[i]*(sum[i]-sum[j])-dis[j]*sum[j];
30        while (l<r && slope(q[r],q[r-1])<slope(i,q[r]))
31            r--;
32        q[++r]=i;
33    }
34    int ans=1023456789;
35    for (int i=1;i<=n;i++) ans=min(ans,dp[i]); 
36    cout << ans <<endl;
37}
38int main(){
39    read();
40    solve();
41    return 0;
42}

转载于:https://www.cnblogs.com/titititing/p/9643668.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值