[COGS 362]锯木厂选址

362. [CEOI2004]锯木厂选址

★★★   输入文件: two.in   输出文件: two.out    简单对比
时间限制:0.1 s   内存限制:32 MB

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

输入

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

输出

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

样例

输入

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

输出

26




dp[i] = min(cost(1, j) + cost(j + 1, i)) + cost(i, n);

令sum[i] = sigma(i=[1, i]){d[i]}
cost[l, r] = sigma{i=[l,r]}(sum[r] - sum[i]) * w[i]
dp[i] = min(sigma{p=[1,j]}{(sum[j] - sum[p]) * w[p]} + sigma{p=[j + 1, i]}{(sum[i] - sum[p]) * w[p]}
dp[i] = min(sigma{p=[1,j]}{sum[j] * w[p] - sum[p] * w[p]} + sigma{p=[j + 1, i]}{sum[i] * w[p] - sum[p] * w[p]}
令c[r] = sigma{i=[1,r]}{sum[i] * w[i]}
令Sw[r] = sigma{i=[1,r]}{w[i]}


则dp[i] = min(sum[j] * Sw[j] - c[j] + sum[i] * (Sw[i] - Sw[j]) - (c[i] - c[j])
dp[i] = min((sum[j] - sum[i]) * Sw[j]) + sum[i] * Sw[i] - c[i];
假设k>j且k优于j,则有Sw[k] > Sw[j], c[k] > c[j]


(sum[j] - sum[i]) * Sw[j] > (sum[k] - sum[i]) * Sw[k]
(sum[j] - sum[i]) * Sw[j] > (sum[k] - sum[i]) * Sw[k]
sum[i] * (Sw[k] - Sw[j]) > sum[k] * Sw[k] - sum[j] * Sw[j]
sum[i] > (sum[k] * Sw[k] - sum[j] * Sw[j]) / (Sw[k] - Sw[j])

斜率是递增的

然而并没有什么卵,推出来了还搞了半天的大于小于号= =(我一定是傻了)

注意如果sum[i] > (sum[k] * Sw[k] - sum[j] * Sw[j]) / (Sw[k] - Sw[j])这个式子成立的话那么就要把队头pop掉

然后队尾的处理是贪心取最小的那一个


#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxn 20010
using namespace std;

int n;
long long w[maxn], d[maxn], sum[maxn], Sw[maxn], c[maxn];

int q[maxn];

long long cost(int l, int r){
	return sum[r] * (Sw[r] - Sw[l - 1]) - c[r] + c[l - 1];
}

double slop(int j, int k){return (double)(sum[k] * Sw[k] - sum[j] * Sw[j]) / (Sw[k] - Sw[j]);}

int main(){
	freopen("two.in", "r", stdin);
	freopen("two.out", "w", stdout);
	//printf("%.3lf\n", (sizeof w)*10 / 1024.0/1024.0);
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++){
		scanf("%lld%lld", &w[i], &d[i]);
		sum[i + 1] = sum[i] + d[i];
		Sw[i] = Sw[i - 1] + w[i];
		c[i] = c[i - 1] + sum[i] * w[i];
	}
	c[n + 1] = c[n];
	Sw[n + 1] = Sw[n];
	long long ans = 0x7fffffff;
	int l = 1, r = 0;
	for(int i = 1; i <= n; i ++){
		while(l < r && slop(q[l], q[l + 1]) <= sum[i])
			l ++;
		ans = min(ans, cost(i + 1, n + 1) + (sum[q[l]] - sum[i]) * Sw[q[l]] + sum[i] * Sw[i] - c[i]);
		while(l < r && slop(q[r - 1], q[r]) > slop(q[r], i))
			r --;
		q[++ r] = i;
	}
	printf("%lld\n", ans);
	return 0;
}



然后附加一道题[ZJOI 2007]仓库建设

一定要注意!!决策0一定要提前加入队列= =


dp[i] = min(dp[j] + cost(j + 1, i)) + c[i]
dp[i] = min(dp[j] + sigma([p=[j + 1, i]] (sum[i] - sum[p]) * w[p])) + c[i]
令C[r] = sigma{i=[1,r]}{sum[i] * w[i]}
令Sw[r] = sigma{i=[1,r]}{w[i]}
dp[i] = min(dp[j] + sum[i] * (Sw[i] - Sw[j]) - C[i] + C[j]) + c[i]
假设k>j且k优于j,则有Sw[k] > Sw[j], c[k] > c[j]
dp[j] + sum[i] * (Sw[i] - Sw[j]) - C[i] + C[j] > dp[k] + sum[i] * (Sw[i] - Sw[k]) - C[i] + C[k]
dp[j] - sum[i] * Sw[j])+ C[j] > dp[k] - sum[i] * Sw[k] + C[k]
sum[i] * (Sw[k] - Sw[j]) > dp[k] - dp[j] + C[k] - C[j]
sum[i] > (dp[k] - dp[j] + C[k] - C[j]) / (Sw[k] - Sw[j])


3
0 5 10
5 3 100
9 6 10



#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxn 1000010
using namespace std;

typedef long long ll;
void read(ll &num){
	char ch = getchar();num = 0;
	for(; ch < '!'; ch = getchar());
	int flag = 1;
	if(ch == '-'){flag = -1, ch = getchar();}
	for(; ch > '!'; ch = getchar())
		num = num * 10 + ch - 48;
	num = num * flag;
}

int n;

ll sum[maxn], Sw[maxn], C[maxn], w[maxn], c[maxn], dp[maxn];

int q[maxn];

double slop(int j, int k){
	return (double) (dp[k] + C[k] - dp[j] - C[j]) / (Sw[k] - Sw[j]);
}

int main(){
    freopen("storage.in", "r", stdin);
	freopen("storage.out", "w", stdout);
	scanf("%d", &n);
	for(int i = 1; i <= n; i ++){
		read(sum[i]);
		read(w[i]);
		read(c[i]);
		Sw[i] = Sw[i - 1] + w[i];
		C[i] = C[i - 1] + w[i] * sum[i];
	}
	    
	int l = 1, r = 0;
	q[++ r] = 0;
	for(int i = 1; i <= n; i ++){
		while(l < r && slop(q[l], q[l + 1]) < sum[i])
		    l ++;
		int j = q[l];
		dp[i] = dp[j] + sum[i] * (Sw[i] - Sw[j]) - C[i] + C[j] + c[i];
		while(l < r && slop(q[r - 1], q[r]) > slop(q[r], i))
		    r --;
		q[++ r] = i;
	}
	
	printf("%lld\n", dp[n]);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值