看到题目,容易写出朴素的规划方程:
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],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;
}