题意
有一排编号为 \(1\sim n\) 的座位。有 \(k\) 种饮料,第 \(i\) 名乘客想要喝第 \(a_i\) 种饮料。小推车需要从 \(0\) 位置出发,最终走到 \(n+1\) 位置,按顺序给每名乘客分饮料。推车上有 \(m\) 个瓶子,每个瓶子可以装 \(p\) 个单位的某一种饮料。推车行进过程中可以回储藏室给空瓶子补充饮料,储藏室可能位于 \(0\) 位置,或 \(n+1\) 位置,或两边都有。在补充饮料后,小推车应该回到第一个未提供饮料的乘客的位置。小推车初始时可以装饮料。求小推车从 \(0\) 走到 \(n+1\),使得每名乘客都能分到饮料的最小总移动距离。\(3\leq n\leq 10^6\),\(1\leq p\leq 10^6\),\(1\leq a_i\leq k\leq m\leq 10^6\)。
题解
NOIP 模拟赛 T3。赛时做出来了,因为一些细节挂了 \(22\) 分。
显然考虑 DP。令 \(f_i\) 表示考虑了前 \(i\) 个人,强制在 \(i\) 处分完饮料后补充饮料,然后回到 \(i+1\) 处的最小距离。枚举 \(j\) 表示上一次在 \(j-1\) 处补充饮料,那么转移就是 \(f_i\gets\min(f_i,f_{j-1}+i-j+dis_i)\),其中 \(dis_i\) 表示从 \(i\) 走到最近的储藏室再回到 \(i+1\) 所走的距离。那么问题转化成判定在 \(j-1\) 处补充饮料后,能否在中途不补充饮料的情况下走完 \([j,i]\)。
我们每次只能给空瓶子补充饮料,乍一看有点棘手。不妨对着一种饮料考虑,我们显然可以把策略固定为:每次从所有装着该饮料的瓶子中选择唯一的不是满瓶的那个,若都是满瓶则任选一瓶。我们发现策略固定后,不管何时补充饮料,在某个固定的座位分这种饮料时,对应的饮料瓶内的饮料量是已知的。
这样性质就很好了,我们可以做一些静态的预处理。令 \(g_i\) 表示前 \(i\) 个人需要开多少瓶饮料,\(h_i\) 表示前 \(i\) 个人能喝完多少瓶饮料。那么在 \(j-1\) 处回储藏室时,有 \(g_{j-1}-h_{j-1}\) 个瓶子是不能补充饮料的,而从 \(j\) 走到 \(i\) 的过程中会喝完 \(g_i-g_{j-1}\) 瓶饮料,于是我们的判定条件实际上就是 \(g_{j-1}-h_{j-1}+g_i-g_{j-1}=g_i-h_{j-1}\leq m\)。
这样我们就能得到 \(\mathcal{O}(n^2)\) 的做法了。
优化是容易的,很显然 \(g,h\) 都单调不减,因此随着 \(i\) 递增,\(j\) 的取值区间的左右端点都单调不减,所以直接使用单调队列即可优化至 \(\mathcal{O}(n)\)。
代码
#include <bits/stdc++.h>using namespace std;#define lowbit(x) ((x) & -(x))
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef pair<int, int> pii;
const int N = 2e6 + 5;
const ll INF = 2e18;template<typename T> inline void chk_min(T &x, T y) { x = min(x, y); }
template<typename T> inline void chk_max(T &x, T y) { x = max(x, y); }int n, m, k, p, c, cnt[N];
int g[N], h[N];
int hd, tl, q[N];
ll f[N];inline int getd(int x) {if (x == n + 1) return 0;if (c == 1) return (n - x) * 2 + 1;if (c == 2) return x * 2 + 1;return min(x * 2 + 1, (n - x) * 2 + 1);
}int main() {ios::sync_with_stdio(0), cin.tie(0);cin >> n >> m >> k >> p >> c;for (int i = 1, x; i <= n; ++i) {cin >> x;g[i] = g[i - 1], h[i] = h[i - 1];if (!cnt[x]++) ++g[i];if (cnt[x] == p) cnt[x] = 0, ++h[i];}f[0] = 1, g[n + 1] = g[n];q[hd = tl = 1] = 0;for (int i = 1; i <= n + 1; ++i) {int d = getd(i);while (hd <= tl && g[i] - h[q[hd]] > m) ++hd;if (hd <= tl) f[i] = f[q[hd]] - q[hd] - 1 + d + i;while (hd <= tl && f[q[tl]] - q[tl] - 1 >= f[i] - i - 1) --tl;q[++tl] = i;}cout << f[n + 1];return 0;
}