[IOI 2000] 邮局原始版、加强版、加强加强版

原始版
令 \(f[i][j]\) 表示前 \(j\) 个村庄放了 \(i\) 个邮局的答案。
有一个很经典的结论,就是第 \(l\sim r\) 个村庄的邮局放在第 \(\left\lfloor \dfrac{l + r}{2}\right\rfloor\) 个村庄那里肯定最优。
因此一个段内的贡献可以 \(\mathcal O(1)\) 求出来(前缀和)。
所以我们考虑把所有村庄分为 \(p\) 个段, \(f[i][j] = \displaystyle\min_{k < j}\{f[i - 1][k] + calc(k + 1, j)\}\)。
可是我们这样搞不会出现一个村庄离右边的邮局更近,但算的却是与左边邮局的距离吗?
就像这样(村2离邮1更近,但算的却是与邮2的距离,且村1、村2、村3不一定挨着):

不会!
因为我们的dp肯定保证了最优性。
这种情况我们把分割线往右移一个村子就可以最优。
因此这种情况不会存在。
我们直接暴力转移 \(\mathcal O(V^2P)\) 即可。
加强版
这个 \(calc\) 实际上满足四边形不等式。
即:
假设若 \(a\le b \le c\le d\),\(w_{a, d} + w_{b, c}\ge w_{a, c} + w_{b, d}\)。
证明(不是很严谨):
假设长这样

你把所有的贡献拆开,忽略那些向下取整,再进行消项。
你会得到一个形似
的东西。
因为 \(a_i\) 单调增,所以这个成立。

(引自这篇题解)
这里面的 \(w\) 其实也就等价于 \(calc\) 啦。
所以令 \(opt[i][j]\) 为 \(f[i][j]\) 的决策点,则 \(opt[i - 1][j]\leq opt[i][j]\leq opt[i][j + 1]\)。
我们对每个 \(i, j\) 枚举 \(k\in[opt[i - 1][j], opt[i][j + 1]]\) 即可。
这样就可以做到 \(\mathcal O(VP)\)。
点击查看代码
#include <iostream>
#include <algorithm>
using std::cin;
using std::cout;
const int N = 3e3 + 10;
typedef long long ll;
const ll oo = 1e18;
int a[N];
ll sum[N];
int opt[N][N];
ll f[N][N];
ll get(int l, int r)
{
int mid = (l + r) >> 1;
return 1ll * a[mid] * (mid - l) - sum[mid - 1] + sum[l - 1] + sum[r] - sum[mid] - 1ll * a[mid] * (r - mid);
}
int main()
{
int v, p;
cin >> v >> p;
for (int i = 1; i <= v; ++i)
cin >> a[i];
std::sort(a + 1, a + v + 1);
for (int i = 1; i <= v; ++i)
sum[i] = sum[i - 1] + a[i];
for (int i = 0; i <= p; ++i)
{
for (int j = 0; j <= v; ++j)
f[i][j] = oo;
}
f[0][0] = 0;
for (int i = 1; i <= p; ++i)
{
for (int j = v; j >= 1; --j)
{
int nowk = 0;
ll nowmin = oo;
for (int k = opt[i - 1][j]; k <= (j == v ? v - 1 : std::min(opt[i][j + 1], j - 1)); ++k)
{
if (f[i - 1][k] + get(k + 1, j) < nowmin)
{
nowk = k;
nowmin = f[i - 1][k] + get(k + 1, j);
}
}
opt[i][j] = nowk;
f[i][j] = nowmin;
}
}
cout << f[p][v] << '\n';
return 0;
}
加强加强版
因为这个 \(calc\) 是有决策单调性的。
再看懂itst神的 https://www.cnblogs.com/Itst/p/12805678.html 。
我们就可以知道这个选 \(m\) 个的限制可以被wqs二分消掉。
令 \(f[i]\) 表示考虑前 \(i\) 个进行分段的答案。
此处我们不用原始版的那个结论了。
我们直接这么转移:
那么令 \(g[j] = \min_{k < j}\{(j - k) * a[j] - (sum[j] - sum[k]) + f[k]\}\),
\(f[i] = \min_{j\le i}\{sum[i] - sum[j] - (i - j) * a[j] + g[j]\}\) 即可。
这两个东西显然可以用斜率优化来做。
复杂度 \(\mathcal O(n\log V)\),\(V\) 是我们二分的值域。
ps:其实利用决策单调性来做二分队列也是可以的,但是我写了没调过,就不放在这了。
点击查看代码
#include <iostream>
#include <algorithm>
#include <cstring>
using std::cin;
using std::cout;
const int N = 5e5 + 10;
typedef long long ll;
typedef __int128 i1;
int n, m;
int a[N];
int qf[N];
int qg[N];
int cntg[N];
int cntf[N];
ll f[N];
ll g[N];
ll sum[N];
i1 Xf(int x)
{
return a[x];
}
i1 Yf(int x)
{
return g[x] + 1ll * x * a[x] - sum[x];
}
i1 Xg(int x)
{
return x;
}
i1 Yg(int x)
{
return f[x] + sum[x];
}
i1 getf(int a, int b, int c)
{
// a -> b, a -> c
// x2 * y1 - x1 * y2
return (Xf(c) - Xf(a)) * (Yf(b) - Yf(a)) - (Xf(b) - Xf(a)) * (Yf(c) - Yf(a));
}
i1 getg(int a, int b, int c)
{
// a -> b, a -> c
// x2 * y1 - x1 * y2
return (Xg(c) - Xg(a)) * (Yg(b) - Yg(a)) - (Xg(b) - Xg(a)) * (Yg(c) - Yg(a));
}
void check(ll mid)
{
memset(f, 0, sizeof(f));
memset(g, 0, sizeof(g));
memset(cntf, 0, sizeof(cntf));
memset(cntg, 0, sizeof(cntg));
int hdg = 1, tlg = 0;
int hdf = 1, tlf = 0;
qg[++tlg] = 0;
for (int i = 1; i <= n; ++i)
{
while (hdg < tlg && (Yg(qg[hdg + 1]) - Yg(qg[hdg])) <= (Xg(qg[hdg + 1]) - Xg(qg[hdg])) * a[i])
hdg++;
int j = qg[hdg];
g[i] = f[j] + 1ll * a[i] * (i - j) - (sum[i] - sum[j]);
cntg[i] = cntf[j];
while (hdf < tlf && getf(qf[tlf - 1], qf[tlf], i) >= 0)
tlf--;
qf[++tlf] = i;
while (hdf < tlf && (Yf(qf[hdf + 1]) - Yf(qf[hdf])) <= (Xf(qf[hdf + 1]) - Xf(qf[hdf])) * i)
hdf++;
j = qf[hdf];
cntf[i] = cntg[j] + 1;
f[i] = g[j] + sum[i] - sum[j] - 1ll * (i - j) * a[j] + mid;
while (hdg < tlg && getg(qg[tlg - 1], qg[tlg], i) >= 0)
tlg--;
qg[++tlg] = i;
}
}
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; ++i)
cin >> a[i];
std::sort(a + 1, a + n + 1);
for (int i = 1; i <= n; ++i)
sum[i] = sum[i - 1] + a[i];
ll l = 0, r = 1e13;
ll ans = 0;
while (l <= r)
{
ll mid = (l + r) >> 1;
check(mid);
if (cntf[n] >= m)
{
ans = f[n] - 1ll * m * mid;
l = mid + 1;
}
else
r = mid - 1;
}
cout << ans << '\n';
return 0;
}

浙公网安备 33010602011771号