Description
有一天 Masha 回到家,发现有 n 只老鼠在它公寓的走廊上,她大声呼叫,所以老鼠们都跑进了走廊的洞中。
这个走廊可以用一个数轴来表示,上面有 n 只老鼠和 m 个老鼠洞。第 i 只老鼠有一个坐标 xi ,第 j 个洞有一个坐标 yj 和容量 cj 。容量表示最多能容纳的老鼠数量。
找到让老鼠们全部都进洞的方式,使得所有老鼠运动距离总和最小。老鼠 i 进入洞 j 的运动距离 为 ∣xi−yj∣。
无解输出 −1。
n,m≤106。
Solution
首先直接贪心就考虑每只老鼠选离它左边最近的或右边最近的钻,但是这样不是全局最优的。
考虑反悔贪心。
先把老鼠和洞放到一起按坐标从小到大排序,然后从前往后扫。设 lstxi 表示第 i 只老鼠当前的最优答案,lstyi 表示第 i 个洞当前的最优答案。然后用两个堆分别维护老鼠和洞的信息。
如果当前枚举到了第 i 只老鼠,那么就要选一个洞匹配,设其为 j,那么匹配 (i,j) 对答案的贡献就是 (xi−yj)−lstyj=xi−(yj+lstyj),于是只要用洞堆维护最大的 yj+lstyj,然后把答案加上 xi−(yj+lstyj),同时 lstxi←xi−lstyj。由于当前老鼠还要反悔,所以把 xi+lstxi 放到老鼠堆里面。
如果枚举到了第 i 个洞是一样的,如果选了 j 号老鼠,贡献就是 yi−(xj+lstxj),用老鼠堆维护最大的 xj+lstxj 即可,然后把答案加上 yi−(xj+lstxj),同时 lstyi←yi−lstxj。
至于如何处理有一个坐标有很多个洞的问题,就每次只往堆里面放一个洞,如果这个匹配了就把匹配的放进去,然后如果还有洞就再选一个没匹配的洞放进去。
时间复杂度:O((n+m)log(n+m))。
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
| #include <bits/stdc++.h>
#define int int64_t
const int kMaxN = 1e6 + 5, kInf = 1e16;
int n, m, mm; int x[kMaxN]; std::pair<int, int> h[kMaxN], a[kMaxN]; std::priority_queue<int> qm; std::priority_queue<std::pair<int, int>> qh;
void dickdreamer() { std::cin >> n >> m; for (int i = 1; i <= n; ++i) { std::cin >> x[i]; a[++mm] = {x[i], 0}; } int sumc = 0; for (int i = 1; i <= m; ++i) { std::cin >> h[i].first >> h[i].second; a[++mm] = {h[i].first, i}; sumc += h[i].second; } if (sumc < n) { std::cout << "-1\n"; return; } std::sort(a + 1, a + 1 + mm); int ans = 0; for (int i = 1; i <= mm; ++i) { if (!a[i].second) { int val = kInf; if (!qh.empty()) { auto [p, id] = qh.top(); qh.pop(); val = a[i].first - p; if (h[id].second) --h[id].second, qh.emplace(h[id].first, id); } ans += val; qm.emplace(a[i].first + val); } else { int val; while (!qm.empty() && h[a[i].second].second && (val = a[i].first - qm.top()) < 0) { --h[a[i].second].second; ans += val; qm.pop(); qh.emplace(a[i].first + val, 0); } if (h[a[i].second].second) --h[a[i].second].second, qh.emplace(a[i].first, a[i].second); } } std::cout << ans << '\n'; }
int32_t main() { #ifdef ORZXKR freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout); #endif std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0); int T = 1; while (T--) dickdreamer(); return 0; }
|