zoukankan      html  css  js  c++  java
  • 【Luogu P4360】[CEOI2004]锯木厂选址

    题目大意:

    一座山,山脚下有个锯木厂,山上有 (n) 棵树,把一棵树砍掉的费用是它的重量乘离锯木厂的距离,如果再在山上设置两个锯木厂,求把所有树砍掉的最小费用。

    正文:

    考虑直接用动态规划。设 (f_i) 表示第二个锯木厂设置在 (i) 处的最小费用,(F_{i,j}) 表示从 ((i+1))(j) 这一段的费用(即在 (i)(j) 处建锯木厂时,从 ((i+1))(j) 的费用),那么动态转移方程就是:

    [f_i=operatorname{min}_{j=1}^{i-1}{F_{0,j}+F_{j,i}+F_{i,n+1}} ]

    接下来考虑 (F_{i,j})

    [F_{i,j}=sum_{k=i+1}^{j-1}left(sum_{l=k+1}^{j-1}d_l ight)w_k ]

    其中 (sum_{l=k+1}^{j}d_l),完全可以用前缀和预处理出,这里用 (D_i) 表示(这里 (D_i=sum_{j=1}^{i-1}d_j)):

    [egin{aligned}F_{i,j} & = sum_{k=i+1}^{j-1}left(D_j-D_k ight)w_k \ & = sum_{k=i+1}^{j-1}D_jcdot w_k-D_kcdot w_k \ & = left(sum_{k=i+1}^{j-1}w_k ight)D_j-left(sum_{k=i+1}^{j-1}w_kcdot D_k ight)end{aligned} ]

    其中 (sum_{k=i+1}^{j-1}w_k)(sum_{k=i+1}^{j-1}w_kcdot D_k) 可以用前缀和预处理,这里用 (W_i,S_i) 表示(这里 (S_i=sum_{j=1}^{i}w_jcdot D_j,W_i=sum_{j=1}^{i}w_j)):

    [egin{aligned}F_{i,j} & =left(W_{j-1}-W_i ight)D_j-left(S_{j-1}-S_i ight)\ & = W_{j-1}cdot D_j-W_icdot D_j-S_{j-1}+S_iend{aligned} ]

    则:

    [f_i=min_{j=1}^{i-1}{left(W_{j-1}cdot D_j-W_0cdot D_j-S_{j-1}+S_0 ight)+left(W_{i-1}cdot D_i-W_jcdot D_i-S_{i-1}+S_j ight)+left(W_{n}cdot D_{n+1}-W_icdot D_{n+1}-S_{n}+S_i ight)} ]

    假设有 (j,k(j,k<i))(j) 的决策比 (k) 更优的情况:

    [left(W_{j-1}cdot D_j-W_0cdot D_j-S_{j-1}+S_0 ight)+left(W_{i-1}cdot D_i-W_jcdot D_i-S_{i-1}+S_j ight)+left(W_{n}cdot D_{n+1}-W_icdot D_{n+1}-S_{n}+S_i ight)leqleft(W_{k-1}cdot D_k-W_0cdot D_k-S_{k-1}+S_0 ight)+left(W_{i-1}cdot D_i-W_kcdot D_i-S_{i-1}+S_k ight)+left(W_{n}cdot D_{n+1}-W_icdot D_{n+1}-S_{n}+S_i ight) ]

    最后可求得斜率方程:

    [frac{(W_{j-1} imes D_{j}-S_{j-1}+S_{j})-(W_{i-1} imes D_i-S_{i-1}+S_i)}{W_j-W_i} leq D_i ]

    代码:

    
    double slope(int x, int y)
    {
    	return (double)((w[y - 1] * sumd[y] - a[y - 1] + a[y]) - (w[x - 1] * sumd[x] - a[x - 1] + a[x]) - 0.0) / (w[y] - w[x] + 0.0);
    }
    
    ll ans = 1ll << 60;
    int main()
    {
    //	freopen(".in", "r", stdin);
    //	freopen(".out", "w", stdout);
    	scanf ("%lld", &n);
    	for (int i = 1; i <= n; ++i)
    		scanf ("%lld%lld", &w[i], &d[i]);
    	for (int i = 1; i <= n + 1; ++i)
    	{
    		sumd[i] = sumd[i - 1] + d[i - 1];
    		a[i] = a[i - 1] + w[i] * sumd[i];
    		w[i] += w[i - 1];
    	}
    	for (int i = 1; i <= n; i++)
    	{
    		while(head < tail && slope(que[head], que[head + 1]) <= (double)sumd[i])
    			head++;
    		f[i] = (w[que[head] - 1] * sumd[que[head]] - a[que[head] - 1]) + (w[i - 1] * sumd[i] - w[que[head]] * sumd[i] - a[i - 1] + a[que[head]]) + (w[n] * sumd[n + 1] - w[i] * sumd[n + 1] - a[n] + a[i]);
    		ans = min (ans, f[i]);
    		while(head < tail && slope(que[tail], i) <= slope(que[tail - 1], que[tail]))
    			tail--;
    		que[++tail] = i;
    	}
    	printf ("%lld", ans);
        return 0;
    }
    
    
  • 相关阅读:
    Oracle 常用函数备查
    apt-get/dpkg常用指令备查
    vmware下虚拟机不能上网问题解决
    [转]JAVA并发编程学习笔记之Unsafe类
    solaris 下查看某程序所开端口
    java 守护线程
    Java实现非法访问异常
    Java使用ListIterator逆序ArrayList
    Java实现Map集合二级联动
    Java使用String类格式化当前日期
  • 原文地址:https://www.cnblogs.com/GJY-JURUO/p/12379835.html
Copyright © 2011-2022 走看看