LUOGU 4360 [CEOI2004]锯木厂选址 斜率优化DP
作者:互联网
title
LUOGU 4360
题目描述
从山顶上到山底下沿着一条直线种植了n棵老树。当地的政府决定把他们砍下来。为了不浪费任何一棵木材,树被砍倒后要运送到锯木厂。
木材只能朝山下运。山脚下有一个锯木厂。另外两个锯木厂将新修建在山路上。你必须决定在哪里修建这两个锯木厂,使得运输的费用总和最小。假定运输每公斤木材每米需要一分钱。
你的任务是编写一个程序,从输入文件中读入树的个数和他们的重量与位置,计算最小运输费用。
输入输出格式
输入格式:
输入的第一行为一个正整数n——树的个数(2≤n≤20000)。树从山顶到山脚按照1,2,...,n标号。
接下来n行,每行有两个正整数(用空格分开)。
第i+1行含有:wi ——第i棵树的重量(公斤为单位)和di——第i棵树和第i+1棵树之间的距离,1≤wi≤10000,0≤di≤10000。
最后一颗树的dn,表示第n棵树到山脚的锯木厂的距离。保证所有树运到山脚的锯木厂所需要的费用小于2×109分。
输出格式:
输出最小的运输费用。
输入输出样例
输入样例#1:
9
1 2
2 1
3 3
1 1
3 2
1 6
2 1
1 2
1 1
输出样例#1:
26
说明
样例图示
黑点为锯木厂
本题共有1313个测试点,每个测试点的数据范围如下
测试点1−5:n≤200;
测试点6−7:n≤1000;
测试点7−13:n≤20000;
analysis
大佬们说这道题是斜率优化入门题,所以蒟蒻来试试,没想到一试就是一下午,o(╥﹏╥)o。
好了,说下想法,看到这题,其实很直接就想到设f[i]表示把锯木厂建在第i棵树的位置的最小费用。
然后就是朴素的状态转移方程了:
f[i]=sum−d[j]∗s[j]−d[i]∗(s[i]−s[j])(j<i)
sum表示所有树一开始全部运送的山脚下的花费,d[i]表示距离的后缀和(因为我们是从上运到下面),s[i]表示树的重量的前缀和。那么在i,j处修了工厂后花费就变成了总花费sum减去从j厂运到山脚的额外花费d[j]∗s[j],再减去从i厂运到山脚下的额外花费d[i]∗(s[i]−s[j])。
形象的说,就是你先把j前面的木材运到jj厂,然后减去这些木材运到山脚的花费,再把i,j之间的木材运到i厂,再减去它们到山脚的花费。
然后我们将DP方程式变形,令j,k(j<k)这两种决策转移到i的时候,k决策更优秀,
那么就可以得到sum−d[k]∗s[k]−d[i]∗(s[i]−s[k])<sum−d[j]∗s[j]−d[i]∗(s[i]−s[j])
整理后可以得出:d[j]∗s[j]−d[k]∗s[k]<d[i]∗(s[j]−s[k])
然后因为斜率d[i]是随着i的增加而变小的,所以我们根据斜率维护一个上凸壳,因为是单调的,所以用一个队列就可以了。
然后根据我调了一下午,最终不得不求助于神犇晗神才A掉此题的前史之鉴,告诉大家,式子一定要自己推,不要看了一遍,觉得会,就直接开始写代码,要把式子写在纸上,会帮很大忙!
code
不得不开long long版
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e4+10;
char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
template<typename T>inline void write(T &x)
{
if (!x) { putchar('0'),putchar('\n'); return ; }
if (x<0) putchar('-'),x=-x;
T num=0,ch[20];
while (x) ch[++num]=x%10+48,x/=10;
while (num) putchar(ch[num--]);
putchar('\n');
}
int w[maxn],d[maxn],s[maxn],q[maxn],f[maxn];
inline ll getUp(int j,int k)
{
return d[j]*s[j]-d[k]*s[k];
}
inline ll getDw(int j,int k)
{
return s[j]-s[k];
}
int main()
{
int n,sum=0;read(n);
for (int i=1; i<=n; ++i) read(w[i]),read(d[i]);
for (int i=n; i>=1; --i) d[i]+=d[i+1];
for (int i=1; i<=n; ++i) s[i]=s[i-1]+w[i],sum+=d[i]*w[i];
int l=1,r=1;
q[1]=0;
for (int i=1; i<=n; ++i)
{
while (l<r && getUp(q[l],q[l+1])<(ll)d[i]*getDw(q[l],q[l+1])) ++l;
f[i]=sum-d[q[l]]*s[q[l]]-d[i]*(s[i]-s[q[l]]);
while (l<r && getUp(q[r-1],q[r])*getDw(q[r],i)<getUp(q[r],i)*getDw(q[r-1],q[r])) --r;
q[++r]=i;
}
int ans=0x7fffffff;
for (int i=1; i<=n; ++i) ans=min(ans,f[i]);
write(ans);
return 0;
}
函数double版
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e4+10;
char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
template<typename T>inline void write(T &x)
{
if (!x) { putchar('0'),putchar('\n'); return ; }
if (x<0) putchar('-'),x=-x;
T num=0,ch[20];
while (x) ch[++num]=x%10+48,x/=10;
while (num) putchar(ch[num--]);
putchar('\n');
}
int w[maxn],d[maxn],s[maxn],q[maxn*5],f[maxn];
inline double slope(int j,int k)
{
return 1.0*(d[j]*s[j]-d[k]*s[k])/(s[j]-s[k]);
}
int main()
{
int n,sum=0;read(n);
for (int i=1; i<=n; ++i) read(w[i]),read(d[i]);
for (int i=n; i>=1; --i) d[i]+=d[i+1];
for (int i=1; i<=n; ++i) s[i]=s[i-1]+w[i],sum+=d[i]*w[i];
int l=1,r=1;
q[1]=0;
for (int i=1; i<=n; ++i)
{
while (l<r && slope(q[l],q[l+1])>d[i]) ++l;
f[i]=sum-d[q[l]]*s[q[l]]-d[i]*(s[i]-s[q[l]]);
while (l<r && slope(q[r-1],q[r])<slope(q[r],i)) --r;
q[++r]=i;
}
int ans=0x7fffffff;
for (int i=1; i<=n; ++i) ans=min(ans,f[i]);
write(ans);
return 0;
}
标签:ch,4360,LUOGU,sum,锯木厂,lt,iii,getchar 来源: https://blog.csdn.net/huashuimu2003/article/details/90270896