斜率优化DP

先考虑朴素DP方程,

f[i][k]代表第k个厂建在i棵树那里的最小代价,最后答案为f[n+1][3];

f[i][k]=min(f[j][k-1] + 把j+1~i的树都运到i的代价)

首先注意到“把j+1~i的树都运到i的代价”不太方便表达,每次都暴力计算显然是无法承受的,

于是考虑前缀和优化,观察到先运到下一棵树那里,等一会再运下去,和直接运下去是等效的。

设sum[i]代表1 ~ i的树都运到i的代价,

于是根据前缀和思想,猜想我们可以用1 ~ r 的代价与 1 ~ l-1的代价获取l ~ r的代价,

所以要做的就是吧1 ~ l-1 对 1 ~ r产生的贡献给算出来,然后减掉,

考虑先把1 ~ l-1的树都运到l-1,所以这部分的代价是sum[l-1],

然后再把树一次性运到r,那么代价是sum_weight[l-1] * (sum_len[r] - sum_len[l-1]);

总的重量 * 现在要再次运的路程

这里为了表示方便,用$sw$代表sum_weight,用$sl$代表sum_len;

于是用$sum[r]$ 减去这两部分代价就可以得到$l ~ r$ 的代价(把$l ~ r$的树都运到$r$)

代价(l ~ r)$ =  sum[r] - sum[l-1] - sw[l-1] * (sl[r] - sl[l-1]);$

那么如何计算sum ?

也是一样的思想,用前面的推后面的,先得到前面的代价,再加上新增的代价即可

$sum[i]=sum[i-1] + swt[i-1] * len[i-1];$//len[i-1]代表i-1到i的距离

于是我们就得到了DP方程:

当$k==1$时,$f[i][k]=sum[i]$;

else

$f[i][k]=min(f[j][k-1] + sum[i] - sum[j] - sw[j] * (sl[i] - sl[j]));$

但是可以发现,由于k最大就是3,而且3必须是n+1才可以取,

而且当$k==1$时,$f[i][k]$就等于$sum[i]$,

所以考虑优化维数:

当$k==1$时,不用求,因为有$sum$了

当$k==2$时,调用的$f[j][k-1]$替换为$sum[j]$,并且还可以发现由于后面有一个$-sum[j]$,所以可以直接消掉

当$k==3$时,由于只有$n+1$可以取,所以直接在外面多写一个循环,相当于最后统计答案即可

转移方式同朴素方程

但是这样是$n^2$的DP,而$n$有20000,那怎么办呢?

考虑斜率优化。

首先我们用暴力打表可以发现,决策是单调的,

打表代码(朴素DP):

 #include<bits/stdc++.h>
using namespace std;
#define R register int
#define AC 20100
int n, ans;
int sum[AC], sum_weight[AC], sum_len[AC], f[AC];
int weight[AC], len[AC];
inline int read()
{
int x = ; char c = getchar();
while(c < '' || c > '') c = getchar();
while(c >= '' && c <= '') x = x * + c - '', c = getchar();
return x;
} void pre()
{
n = read();
for(R i = ; i <= n; i ++)
weight[i] = read(), len[i] = read();
} void getsum()
{
for(R i = ; i <= n + ; i ++)//山脚的也要求
{
sum_len[i] = sum_len[i - ] + len[i - ];
sum_weight[i] = sum_weight[i - ] + weight[i];
sum[i] = sum[i - ] + sum_weight[i - ] * len[i - ];
// printf("%d : %d\n",i,sum[i]);
}
} void work()
{
for(R i = ; i <= n; i ++)
{
int tmp = ;
f[i] = INT_MAX;
for(R j = ;j < i;j ++)
{
if(sum[i] - sum_weight[j] * (sum_len[i] - sum_len[j]) < f[i])
{
f[i] = sum[i] - sum_weight[j] * (sum_len[i] - sum_len[j]);
tmp = j;
}
}
printf("%d --- > %d\n", tmp, i);//打表验证决策单调性
}
ans = INT_MAX;
for(R i = ; i <= n; i ++)//注意应该是n+1,因为山脚是在下面
ans = min(ans, f[i] + sum[n + ] - sum[i] - sum_weight[i] * (sum_len[n + ] - sum_len[i]));
for(R i = ; i <= n; i ++) printf("%d : %d\n", i, f[i]);
printf("%d\n", ans);
} int main()
{
freopen("in.in", "r", stdin);
freopen("out.out", "w", stdout);
pre();
getsum();
work();
fclose(stdin);
fclose(stdout);
return ;
}

于是我们推斜率优化方程:

设有 $k < j < i$,且$j$优于$k$(相当于$j$是后面来的),则有:

$sum[i] - sw[j] * (sl[i] - sl[j]) < sum[i] - sw[k] * (sl[i] - sl[k])$

$sw[j] * (sl[i] - sl[j]) > sw[k] * (sl[i] - sl[k])$

$sw[j] * sl[i] - sw[j] * sl[j] >  sw[k] * sl[i] - sw[k] * sl[k]$

$sw[k] * sl[k] - sw[j] * sl[j] > sw[k] * sl[i] - sw[j] * sl[i]$

$sw[k] * sl[k] - sw[j] * sl[j] > sl[i] * (sw[k] - sw[j])$

$\frac{(sw[k] * sl[k] - sw[j] * sl[j])} {(sw[k] - sw[j])} < sl[i]$ //注意sw[k] - sw[j]小于0,要变号

所以令$K = \frac{(sw[k] * sl[k] - sw[j] * sl[j])}{(sw[k] - sw[j])}$;

则    while(head < tail && k(q[head],q[head+1]) < sum_len[i])  ++head;

while(head < tail && k(q[tail-1],q[tail]) > k(q[tail],i)) --tail;

最后上代码:

 #include<bits/stdc++.h>
using namespace std;
#define R register int
#define AC 20100
int n, ans;
int sum[AC], sum_weight[AC], sum_len[AC], f[AC];
int weight[AC], len[AC];
int q[AC], head, tail;
inline int read()
{
int x = ; char c = getchar();
while(c < '' || c > '') c = getchar();
while(c >= '' && c <= '') x = x * + c - '', c = getchar();
return x;
} inline double k(int x, int y)
{
double a = sum_weight[x] * sum_len[x] - sum_weight[y] * sum_len[y];
double b = sum_weight[x] - sum_weight[y];
return a / b;
} void pre()
{
n = read();
for(R i = ; i <= n; i ++) weight[i] = read(), len[i] = read();
} void getsum()
{
for(R i = ; i <= n + ; i ++)//山脚的也要求
{
sum_len[i] = sum_len[i - ] + len[i - ];
sum_weight[i] = sum_weight[i - ] + weight[i];
sum[i] = sum[i - ] + sum_weight[i - ] * len[i - ];
// printf("%d : %d\n",i,sum[i]);
}
} void work()
{
head=;
for(R i = ; i <= n; i ++)
{
f[i] = INT_MAX; /*int tmp = 0;
f[i] = INT_MAX;
for(R j = 1; j < i; j ++)
{
if(sum[i] - sum_weight[j] * (sum_len[i] - sum_len[j]) < f[i])
{
f[i] = sum[i] - sum_weight[j] * (sum_len[i] - sum_len[j]);
tmp = j;
}
}
printf("%d --- > %d\n", tmp, i);//打表验证决策单调性*/ while(head < tail && k(q[head], q[head + ]) < sum_len[i]) ++ head;
int now = q[head];
// printf("%d --- > %d\n",now,i);
f[i] = sum[i] - sum_weight[now] * (sum_len[i] - sum_len[now]);
while(head < tail && k(q[tail - ], q[tail]) > k(q[tail], i)) -- tail;
q[++tail] = i;
}
ans = INT_MAX;
for(R i = ; i <= n; i ++)//注意应该是n+1,因为山脚是在下面,注意要从2开始,因为这是在枚举第2个厂在哪
ans = min(ans, f[i] + sum[n + ] - sum[i] - sum_weight[i] * (sum_len[n + ] - sum_len[i]));
printf("%d\n", ans);
} int main()
{
// freopen("in.in", "r", stdin);
pre();
getsum();
work();
// fclose(stdin);
return ;
}

---------------2018.10.12--------------优化了代码格式

[CEOI2004]锯木厂选址 斜率优化DP的更多相关文章

  1. P4360 [CEOI2004]锯木厂选址

    P4360 [CEOI2004]锯木厂选址 这™连dp都不是 \(f_i\)表示第二个锯木厂设在\(i\)的最小代价 枚举1号锯木厂 \(f_i=min_{0<=j<i}(\sum_{i= ...

  2. luoguP4360 [CEOI2004]锯木厂选址

    题目链接 luoguP4360 [CEOI2004]锯木厂选址 题解 dis:后缀和 sum:前缀和 补集转化,减去少走的,得到转移方程 dp[i] = min(tot - sumj * disj - ...

  3. 2018.08.28 洛谷P4360 [CEOI2004]锯木厂选址(斜率优化dp)

    传送门 一道斜率优化dp入门题. 是这样的没错... 我们用dis[i]表示i到第三个锯木厂的距离,sum[i]表示前i棵树的总重量,w[i]为第i棵树的重量,于是发现如果令第一个锯木厂地址为i,第二 ...

  4. 洛谷4360[CEOI2004]锯木厂选址 (斜率优化+dp)

    qwq 我感觉这都已经不算是斜率优化\(dp\)了,感觉更像是qwq一个\(下凸壳优化\)转移递推式子. qwq 首先我们先定义几个数组 \(sw[i]\)表示\(w[i]\)的前缀和 \(val[i ...

  5. 洛谷P4360 [CEOI2004]锯木厂选址(斜率优化)

    传送门 我可能根本就没有学过斜率优化…… 我们设$dis[i]$表示第$i$棵树到山脚的距离,$sum[i]$表示$w$的前缀和,$tot$表示所有树运到山脚所需要的花费,$dp[i]$表示将第二个锯 ...

  6. 动态规划(斜率优化):[CEOI2004]锯木厂选址

    锯木场选址(CEOI2004) 从山顶上到山底下沿着一条直线种植了n棵老树.当地的政府决定把他们砍下来.为了不浪费任何一棵木材,树被砍倒后要运送到锯木厂. 木材只能按照一个方向运输:朝山下运.山脚下有 ...

  7. [BZOJ2684][CEOI2004]锯木厂选址

    BZOJ权限题! Description 从山顶上到山底下沿着一条直线种植了n棵老树.当地的政府决定把他们砍下来.为了不浪费任何一棵木材,树被砍倒后要运送到锯木厂. 木材只能按照一个方向运输:朝山下运 ...

  8. LG4360 [CEOI2004]锯木厂选址

    题意 原题来自:CEOI 2004 从山顶上到山底下沿着一条直线种植了 n 棵老树.当地的政府决定把他们砍下来.为了不浪费任何一棵木材,树被砍倒后要运送到锯木厂. 木材只能朝山下运.山脚下有一个锯木厂 ...

  9. luogu P4360 [CEOI2004]锯木厂选址

    斜率优化dp板子题[迫真] 这里从下往上标记\(1-n\)号点 记\(a_i\)表示前缀\(i\)里面树木的总重量,\(l_i\)表示\(i\)到最下面的距离,\(s_i\)表示\(1\)到\(i-1 ...

随机推荐

  1. eclipse+tomcat配置远程debug调整

    由于开发环境与真实服务器环境存在差异,有时开发时明明正常的逻辑,部署之后就会出现各种各样的问题,通过日志邮不能明确定位到问题的时候,可以采用远程debug调试来定位问题.下面就介绍一下具体的配置步骤: ...

  2. unity面试题二

    1.以下哪一个选项不属于Unity引擎所支持的视频格式文件(D) A.后缀为mov的文件 B.后缀为mpg的文件 C.后缀为avi的文件 D.后缀为swf的文件 2.Unity引擎使用的是左手坐标系还 ...

  3. Visual Studio 起始页中不显示最近使用的项目的解决办法

    将 HKEY_CURRENT_USER/Software/Microsoft/Windows/CurrentVersion/Policies/Explorer/NoRecentDocsHistory的 ...

  4. Qt-QML-Button-ButtonStyle-实现鼠标滑过点击效果

    上次实现的自定义的Button功能是用的自定义的Rectangle来实现的,在慢慢的接触了QML之后,发现QML有自己定义的Button 这里盗版贴上Qt帮助文档中的部分关于Button的属性内容 B ...

  5. 如何理解一台服务器可以绑定多个ip,一个ip可以绑定多个域名

    一个域名只能对应一个IP的意思是域名在DNS服务器里做解析的时候 一条记录只能指向一个IP地址.这个是死规定,试想一下,如果一个子域名指向了2个ip ,当访问者打开这个域名的时候,浏览器是展示哪个IP ...

  6. 数数字 (Digit Counting,ACM/ICPC Dannang 2007 ,UVa1225)

    题目描述:算法竞赛入门经典习题3-3 #include <stdio.h> #include <string.h> int main(int argc, char *argv[ ...

  7. CsvHelper文档-2读

    CsvHelper文档-2读 这个库默认不需要做任何设置就可以很容易的使用它.如果你的类属性名称直接匹配csv的标题名称,那么可以按照下面的实例来用: (以下所有的代码都需要引用using csvhe ...

  8. Intro to Probabilistic Model

    概率论复习 概率(Probability) 频率学派(Frequentist):由大量试验得到的期望频率(致命缺陷:有些事情无法大量试验,例如一封邮件是垃圾邮件的概率,雷达探测的物体是一枚导弹的概率) ...

  9. Python高级编程-使用SQLite

    SQLite是一种嵌入式数据库,它的数据库就是一个文件.由于SQLite本身是C写的,而且体积很小,所以,经常被集成到各种应用程序中,甚至在iOS和Android的App中都可以集成. Python就 ...

  10. Python3 数据类型-字典

    字典是一种可变数据类型,且可存储任意类型对象. 字典使用大括号"{}"括起来,由键(key)和值(values)组成,键只能使用不可变类型定义,值可以使用可变类型{'键':'值'} ...