题解 P4767 【[IOI2000]邮局】
读完题我们会发现这个题可以用wqs二分来优化dp。
我们会看出两个性质
设
第一个非常的显然。
第二个结论我们考虑每次放邮局的过程,放入当前邮局会使一段区间内的村庄减小代价,而我们要找到减小代价最多的这一个区间,而我们上一次放入使得这个区间变多了,如果说上一次放的时候有一个区间比我这次放的区间优,那么上一次我就会放这个区间,所以说这次减少的代价一定比上一次少。
定义
于是我们二分斜率
那么我们设
那么可以轻松地写出转移方程
其中
这样子直接转移是
首先先证明
证明:
设
然后注意到
记
于是移项得
而
证毕
然后我们关注到一个性质:一个点作为最优点转移到的点一定构成一个区间,也就是对于一个
如何证明这个结论,我们考虑反证法。(后面的作为最优解转移直接写作转移)
证明:
假设存在
那么可得几个不等式
整理得
而由于
证毕
然后我们可以用队列维护之前的决策点可以转移到哪个区间,到一个新的点的时候就二分找出这个点可以被哪个点转移,然后再二分找到这个点可以转移到的区间放进队列里。
这样复杂度是
Code
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#define reg register
const int N = 3e3;
const int M = 300;
using namespace std;
struct que
{
int p,l,r;
}q[N + 5];
int n,m,a[N + 5],s[N + 5],f[N + 5],pre[N + 5],ans;
inline int dist(int l,int r)
{
int mid = l + r >> 1;
return s[r] - s[mid] - a[mid] * (r - mid) + a[mid] * (mid - l) - (s[mid - 1] - s[l - 1]);
}
inline int check(int k)
{
int R = 0;
q[++R] = (que){0,1,n};
for (reg int i = 1;i <= n;i++)
{
int l = 1,r = R,p,mid;
while (l <= r)
{
mid = l + r >> 1;
if (q[mid].l <= i)
l = mid + 1,p = mid;
else
r = mid - 1;
}
f[i] = f[q[p].p] + dist(q[p].p + 1,i) + k;
pre[i] = pre[q[p].p] + 1;
p = 0;
while (R && f[i] + dist(i + 1,q[R].l) + k <= f[q[R].p] + dist(q[R].p + 1,q[R].l) + k)
p = q[R--].l;
if (R && f[i] + dist(i + 1,n) + k <= f[q[R].p] + dist(q[R].p + 1,n) + k)
{
l = q[R].l,r = n;
while (l <= r)
{
mid = l + r >> 1;
if (f[i] + dist(i + 1,mid) + k <= f[q[R].p] + dist(q[R].p + 1,mid) + k)
r = mid - 1,p = mid;
else
l = mid + 1;
q[R].r = p - 1;
}
}
if (p)
q[++R] = (que){i,p,n};
}
return pre[n];
}
int main()
{
scanf("%d%d",&n,&m);
for (reg int i = 1;i <= n;i++)
scanf("%d",&a[i]),s[i] = s[i - 1] + a[i];
int l = 0,r = 3e7,mid;
while (l <= r)
{
mid = l + r >> 1;
if (check(mid) >= m)
l = mid + 1,ans = f[n] - m * mid;
else
r = mid - 1;
}
cout<<ans<<endl;
return 0;
}