P4360 [CEOI 2004] 锯木厂选址

思路

首先考虑暴力DP,我们预处理出从第 \(1\) 个点到第 \(n\) 个点木材重量、运输距离与运输费用的前缀和,枚举两个锯木厂的位置,设其分别在 \(i\) 点和 \(j\) 点。

易得状态转移方程为 $$ans=\min{f[j] \times (dis[j]-dis[i])+f[i] \times dis[i]-f[i] \times dis[n+1]+g[n+1]\ }$$ 其中 \(f[i]\) 表示木材重量,\(dis[i]\) 表示距离,\(g[i]\) 表示费用,时间复杂度 \(O(n^2)\)。

在P4360中,我们可以使用暴力DP水过该题,然而,加强数据后,明显TLE,考虑优化。

观察式子,我们分为只和 \(j\) 有关的,和 \(i\) 与 \(j\) 都有关的,只和 \(i\) 有关的三部分,可将其变形为 $$f[j] \times dis[j]=f[j] \times dis[i]-f[i] \times dis[i]+f[i] \times dis[n+1]+g[n+1]+ans$$ 对于该式子,可以进行斜率优化,我们想要使 \(-f[i] \times dis[i]+f[i] \times dis[n+1]+g[n+1]+ans\) 最小,也就是令一次函数的截距最小,下凸包维护各点,用斜率 \(dis[i]\) 查找即可,因为 \(dis[i]\) 与 \(f[i]\) 均递增,可用单调队列实现,复杂度 \(O(n)\) 十分优秀。

Code

复制代码
#include<bits/stdc++.h>
#define ll long long
#define lb long double
using namespace std;
const int M=1e5+10;
int n,l;
ll w[M],d[M];
ll f[M],g[M],dis[M];
ll dp[M];
lb slope(int x,int y)
{
	return (lb)((lb)f[y]*dis[y]-(lb)f[x]*dis[x])/(lb)((lb)f[y]-(lb)f[x]);
}
int h=1,t=0,q[M];
int main()
{
	//memset(dp,0x7f,sizeof(dp));
	ll ans=1e18+5;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld%lld",&w[i],&d[i]);
		f[i]=f[i-1]+w[i];
	}
	for(int i=2;i<=n+1;i++)
	{
		g[i]=g[i-1]+f[i-1]*d[i-1];
		dis[i]=dis[i-1]+d[i-1];
	}
	int j=1;
	q[++t]=1;
	for(int i=2;i<=n;i++)
	{
		while(h<t&&slope(q[h],q[h+1])<=dis[i]) h++;
		ans=min(ans,f[q[h]]*(dis[q[h]]-dis[i])+f[i]*dis[i]-f[i]*dis[n+1]+g[n+1]);
		while(h<t&&slope(q[t-1],q[t])>=slope(q[t],i)) t--;
		q[++t]=i;
		//ans=min(ans,f[j]*(dis[j]-dis[i])+f[i]*dis[i]-f[i]*dis[n+1]+g[n+1]);
	}
	printf("%lld\n",ans);
	return 0;
}
/* 
f[j]*dis[j] = f[j]*dis[i]  +  /-f[i]*dis[i]+f[i]*dis[n+1]+g[n+1]+ans/
  y					xk				常数b尽量小 
*/