[BZOJ1096][ZJOI2007]仓库建设

题面戳我

sol

我觉得我还是要来复习一下斜率优化
首先我们要写出\(O(n^2)\)\(DP\)式子
\(f_i\)表示解决前\(i\)个工厂的产品,且一定要在第\(i\)个工厂建设仓库的最小费用

\[f_i=min\{f_j+\sum_{j+1\le l \le i}p_l*(x_i-x_l)\}+C_i(1\le j<i) \]

\(sum_i\)表示\(p_i\)的前缀和,\(tot_i\)表示\(x_i*p_i\)的前缀和。那么上式可以简化为

\[f_i=min\{f_j+x_i(sum_i-sum_j)-(tot_i-tot_j)\}+C_i\\=min\{f_j-x_i*sum_j+tot_j\}+x_isum_i-tot_i+C_i(1\le j<i) \]

我们假设现在有两个决策\(j\)\(k\),其中\(k<j\),且选\(j\)比选\(k\)更优。所以

\[f_j-x_isum_j+tot_j < f_k-x_isum_k+tot_k \]

移一下项

\[\frac {f_j+tot_j-f_k-tot_k}{sum_j-sum_k}<x_i \]

左边那个式子像是什么?斜率?
对于每一个决策点,视\(x_i=sum_i,y_i=f_i+tot_i\),那么每个决策就可以对应二维平面上的一个点
对于相邻的两个决策点,如果满足斜率\(<x_i\),那么\(k\)就不是那个更优的。而因为\(x_i\)单调递增,所以这样\(k\)就永远不能成为最优决策点。所以就可以直接把\(k\)这个决策点弹掉。
用单调队列把时间复杂度优化到\(O(n)\)

code

#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1000005;
#define ll long long
ll gi()
{
	ll x=0,w=1;char ch=getchar();
	while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
	if (ch=='-') w=0,ch=getchar();
	while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
	return w?x:-x;
}
int n,q[N],hd,tl;
ll x[N],P[N],C[N],sum[N],tot[N],f[N];
double calc(int j,int k)
{
	return (double)(f[j]+tot[j]-f[k]-tot[k])/(1.0*(sum[j]-sum[k]));
}
int main()
{
	n=gi();
	for (int i=1;i<=n;i++)
	{
		x[i]=gi();P[i]=gi();C[i]=gi();
		sum[i]=sum[i-1]+P[i];
		tot[i]=tot[i-1]+P[i]*x[i];
	}
	for (int i=1,j;i<=n;i++)
	{
		while (hd<tl&&calc(q[hd],q[hd+1])<x[i]) ++hd;
		j=q[hd];//j是此时的最优决策点
		f[i]=f[j]-x[i]*sum[j]+tot[j]+x[i]*sum[i]-tot[i]+C[i];
		while (hd<tl&&calc(q[tl-1],q[tl])>calc(q[tl],i)) --tl;
		q[++tl]=i;
	}
	printf("%lld\n",f[n]);
	return 0;
}
posted @ 2018-01-22 20:47  租酥雨  阅读(80)  评论(0编辑  收藏