小 P 的牧场 题解
题目描述
小 P 在 MC 里有 \(n\) 个牧场,自西向东呈一字形排列(自西向东用 \(1…n\) 编号),于是他就烦恼了:为了控制这 \(n\) 个牧场,他需要在某些牧场上面建立控制站,每个牧场上只能建立一个控制站,每个控制站控制的牧场是它所在的牧场一直到它西边第一个控制站的所有牧场(它西边第一个控制站所在的牧场不被控制)(如果它西边不存在控制站,那么它控制西边所有的牧场),每个牧场被控制都需要一定的花费(毕竟在控制站到牧场间修建道路是需要资源的嘛~),而且该花费等于它到控制它的控制站之间的牧场数目(不包括自身,但包括控制站所在牧场)乘上该牧场的放养量,在第 \(i\) 个牧场建立控制站的花费是 \(a_i\) ,每个牧场i的放养量是 \(b_i\),理所当然,小 P 需要总花费最小,但是小 P 的智商有点不够用了,所以这个最小总花费就由你来算出啦。
Input
第一行一个整数 \(n\) 表示牧场数目。
第二行包括 \(n\) 个整数,第 \(i\) 个整数表示 \(a_i\)。
第三行包括 \(n\) 个整数,第 \(i\) 个整数表示 \(b_i\)。
Output
只有一行,包括一个整数,表示最小花费。
Sample Input
4
2424
3142
Sample Output
9
样例解释
选取牧场 1,3,4 建立控制站,最小费用为 \(2+(2+1*1)+4=9\)。
数据范围与约定
对于 10% 的数据,\(1<=n<=10\),
对于 30% 的数据,\(1<=n<=1000\),
对于 100% 的数据,\(1<=n<=1000000\) , \(0<ai,bi<=10000\) 。
解题思路
题目中已经告诉了每个牧场建立控制站的花费 \(a_i\)。那么我们设当前位置为 \(i\),在 \(i,j\) 处都建立控制站。对于不同的 \(j\),会有不同的情况。
- 如果 \(j=i-1\),即 \(i,j\) 相邻,那么 \(i\) 处对答案的贡献就是 \(a_i\)。
- 如果 \(j<i-1\),且区间 \((j,i)\) 中没有控制站,不难得出,区间 \([j+1,i]\) 处对答案的贡献为 \(a_i+\sum_{k=j+1}^{i-1}b[k]×(i-k)\)。
这样,状态转移方程就得出了,\(f[i]=f[j]+a[i]+\sum_{k=j+1}^{i-1}b[k]×(i-k)\)。范围 \(1\leq j<i\) 。
如果枚举 \(i,j,k\) 三重循环硬上,可能会获得 10 分的好成绩。很显然,我们需要优化。
对于 \(\sum_{k=j+1}^{i-1}b[k]×(i-k)\),我们可以将其拆解,之后用前缀和优化。我们记 \(sb[i]\) 为 \(b[i]\) 的前缀和 ,\(sib[i]\) 为 \(i×b[i]\) 的前缀和。
下面是对状态转移方程进行拆解并简化的过程:
(上面第二个式子如果写格式的有问题请不要在意,只要懂这个意思就行)
这样,我们就把 \(O(n^3)\) 的复杂度优化成了 \(O(n^2)\)。
可是这样只能拿 30 分,于是现学现卖用了个玄学的斜率优化。具体原理请参见 李煜东《算法竞赛进阶指南》。
假设 \(k<j\), 并且 \(f[j]<f[k]\),即 \(j\) 比 \(k\) 优。
则可以得出:
\(f[j]+a[i]+(sb[i−1]−sb[j])×i−(sib[i−1]−sib[j])<\)
\(f[k]+a[i]+(sb[i−1]−sb[k])×i−(sib[i−1]−sib[k])\)
整理上式得到:
之后使用斜率优化,就可以开心地 AC 了。
Code
#include <头文件>
using namespace std;
typedef long long ll;
inline ll read(){}
const int NN=1000002;
int n, a[NN], b[NN];
ll f[NN], sb[NN], sib[NN],q[NN]; //这些可能会很大,所以开 long long
inline double KK(int j, int k) //斜率
{
return 1.0 * (f[j] + sib[j] - f[k] - sib[k]) / (double)(sb[j] - sb[k]);
}
int main()
{
freopen("pasture.in", "r", stdin);
freopen("pasture.out", "w", stdout);
n = read();
for (int i = 1; i <= n; ++i)
a[i] = read();
for (int i = 1; i <= n; ++i)
{
b[i] = read();
sb[i] = sb[i - 1] + (ll)b[i];
sib[i] = sib[i - 1] + (ll)i * (ll)b[i];
}
ll l = 1, r = 1;
for (int i = 1; i <= n; i++)
{
while (l < r && KK(q[l + 1], q[l]) <= i)
l++;
f[i] = f[q[l]] + a[i] + i * (sb[i - 1] - sb[q[l]]) - (sib[i - 1] - sib[q[l]]);
while (l < r && KK(q[r], q[r - 1]) >= KK(i, q[r]))
r--;
q[++r] = i;
}
printf("%lld\n", f[n]);
return 0;
}
/*
4
2 4 2 4
3 1 4 2
*/
EdisonBa
2021.2.24 初次编辑