[ZJOI2007] 仓库建设
题意
有 \(n\) 个工厂,对于第 \(i\) 个工厂,给出:
- \(x_i\):到第 \(1\) 个工厂的距离
- \(p_i\):第 \(i\) 个工厂的物品数
- \(c_i\):在第 \(i\) 个工厂建仓库的花费
每个工厂的物品只能向它后面的工厂运,运费为运送距离 * 运送物品数量。
必须将每个物品放在仓库里,求最小花费。
\(n\le 1e6\)
思路
蒟蒻最开始设的是 \(f_{i,j}\) ,表示前 \(i\) 个工厂建 \(j\) 个仓库,且第 \(j\) 个仓库建在 \(i\) 的最小花费。
然后显然,
然后就懵了。
时间复杂度:\(O(n^4)!!!\) ,空间复杂度:\(O(n^2)!!!\)
双双爆炸!
经过网上 \(Dalao\) 们题解的启发,我开始了优化。
看到这个式子:
尝试化简
然后发现 \(\sum_{l=k+1}^{i-1}p_l\) 和 \(\sum_{l=k+1}^{i-1}x_{l}\cdot p_l\) 与 \(i\) 并没有任何关系,直接算前缀和。
记 \(sp_i=\sum_{l=1}^i p_l,sxp_i=\sum_{l=1}^i x_l\cdot p_l\)
于是,
时间复杂度:\(O(n^3)!!\) ,空间复杂度:\(O(n^2)!!!\)
还差远了。
但是观察转移方程,每次从 \(j-1\) 转移到 \(j\) ......
啊哈!滚动数组!
直接滚掉一维,空间复杂度:\(O(n)\) ,可以过了。
但时间复杂度还是 \(O(n^3)\) ,炸掉。
但我们又细品状态转移方程,好像状态的转移和 \(j\) 并没有关系......
啊哈!不用枚举 \(j\) 了!
时间复杂度:\(O(n^2)\) !
还差一点。
把方程展开:
看到乘积项 \(x_isp_{k}\) ,想到斜率优化。
尝试一下。
状态变量为 \(i\) ,决策变量为 \(k\)
移项。
此时:
发现 \(k\) 单调增,\(x\) 也单调增。
可行!
时间复杂度:\(O(n)\)
终于拿下了!
Code
#include <bits/stdc++.h>
#define re register
#define int long long
#define ld long double
#define END system("pause")
#define inf 0x7fffffff
using namespace std;
inline int read()
{
re int x=0,f=1;
re char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-48;c=getchar();}
return x*f;
}
const int N=1e6+6;
int n;
int x[N],sp[N],sxp[N],c[N];
int f[N];
int q[N],h,t;
inline int Y(int k){return f[k]+sxp[k];}
//inline int X(int k){return sp[k];}
signed main()
{
n=read();
for(re int i=1,p;i<=n;++i)
{
x[i]=read(),p=read(),c[i]=read();
sp[i]=sp[i-1]+p;
sxp[i]=sxp[i-1]+x[i]*p;
}
q[h=t=1]=0;
for(re int i=1;i<=n;++i)
{
while(h<t&&Y(q[h+1])-Y(q[h])<=x[i]*(sp[q[h+1]]-sp[q[h]])) ++h;
f[i]=f[q[h]]+x[i]*sp[i-1]-x[i]*sp[q[h]]-sxp[i-1]+sxp[q[h]]+c[i];
//q[t-1],q[t],i
while(h<t&& (Y(q[t])-Y(q[t-1])) * (sp[i]-sp[q[t]]) >= (Y(i)-Y(q[t])) * (sp[q[t]]-sp[q[t-1]]) ) --t;
q[++t]=i;
}
printf("%lld",f[n]);
END;
return 0;
}

该文不被密码保护。
浙公网安备 33010602011771号