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

image

原始版

\(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不一定挨着):
image

不会!
因为我们的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}\)
证明(不是很严谨):
假设长这样
image
你把所有的贡献拆开,忽略那些向下取整,再进行消项。
你会得到一个形似

\[\sum^{len1 + len2}_{i = 1}a_i\ge \sum^{len1}_{i = 1}a_i + \sum^{len2}_{i = 1}a_i \]

的东西。
因为 \(a_i\) 单调增,所以这个成立。
image
(引自这篇题解
这里面的 \(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\) 个进行分段的答案。
此处我们不用原始版的那个结论了。
我们直接这么转移:

\[f[i] = \min_{j\le i}\{sum[i] - sum[j] - (i - j) * a[j] + \min_{k < j}\{(j - k) * a[j] - (sum[j] - sum[k]) + f[k]\}\} \]

那么令 \(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;
}
posted @ 2026-06-21 09:53  SigmaToT  阅读(3)  评论(0)    收藏  举报