【ZJOI2007】【斜率优化DP】仓库建设

21 篇文章 0 订阅

看到题目,容易写出朴素的规划方程:

f[i]表示在第i个点建设一个仓库,前i个货物点运送的最小总费用

则f[i]= min(f[j] + w[j][i]) + c[i]

其中w[i][j] = p[j+1][1] *(x[i] - x[j+1]) + p[j+2] *(x[i] - x[j+2]) + .... p[i] * (x[i] - x[i])

但是n的范围非常大,所以需要优化方程。

将w[i][j]变形:

w[j][i]  = p[j+1][i] * (x[i] - x[j+1]) + p[j+2] * (x[i] - x[j+2]) + ... p[i] * (x[i] - x[i])

          = x[i] * (p[j+1] +... +p[i]) - (p[j+1] * x[j+1] + ... p[i] * x[i])

令sump[x] = p[k],sum[x] =p[k] * x[k],∈ [1,x]

-> w[j][i] = x[i] * (sump[i] - sump[j]) - (sum[i]- sum[j])

              = -x[i] * sump[j] + sum[j] + (x[i] * sump[i] - sum[i])

我们忽略可以根据i确定的常量

则f[i] = min{f[j] + sum[j] - x[i] * sump[j]} + x[i] * sump[i] - sum[i] + c[i]

这样设x = sump[j],y =f[j] + sum[j],k = -x[i]

不难证明决策是单调的,我们可以使用双端队列来维护决策,时间复杂度为O(n)

注意这道题所有数组都需要long long,否则会wa

代码:

#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 1000000 + 10;
const long long inf = 0x3f3f3f3f3f3f3f3fll;
long long sump[maxn],sum[maxn];
long long f[maxn];
long long x[maxn],p[maxn],c[maxn],deq[maxn];
int n;
void init()
{
	freopen("bzoj1096.in","r",stdin);
	freopen("bzoj1096.out","w",stdout);
}

void readdata()
{
	scanf("%d",&n);
	for(int i = 1;i <= n;i++)
	{
		scanf("%lld%lld%lld",&x[i],&p[i],&c[i]);
		sump[i] = sump[i-1] + p[i];
		sum[i] = sum[i-1] + p[i] * x[i];
	}
}

long long w(int l,int r)
{
	return (long long)x[r] * (sump[r] - sump[l]) - (sum[r] - sum[l]);
}

double slope(int i,int j)
{
	long long tmp = f[i] + sum[i] - f[j] - sum[j];
	if(sump[i] == sump[j])
	{
		if(tmp >= 0)return inf;
		else return -inf;
	}
	return (double)tmp / (double)(sump[i] - sump[j]);
}

void solve()
{
	memset(f,0,sizeof(f));
	memset(deq,0,sizeof(deq));
	int s = 0,e = -1;
	deq[++e] = 0;
	for(int i = 1;i <= n;i++)
	{
		while(s < e && slope(deq[s+1],deq[s]) <= (double)x[i])
		++s;
		f[i] = f[deq[s]] + w(deq[s],i) + c[i];
		while(s < e && slope(deq[e],deq[e-1]) >= slope(i,deq[e]))e--;
		deq[++e] = i;
	}
	printf("%lld\n",f[n]);
}

int main()
{
	init();
	readdata();
	solve();
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值