Codeforces Round #583 F Employment
原题
https://codeforces.com/contest/1214/problem/F
题目大意
有m座城市围成一个圈。某公司在其中n座城市中各有一个空的办公室;而又恰有n个员工,他们的家位于n个城市中。给出办公地点所在城市与员工所在城市,问如何规划每个员工的上班地点,才能使得他们上班的总距离最小。这些城市有可能相同。
数据规模:\(m \le 10^9\) \(n \le 2 \times 10^5\)
题解
这就是一道智商题,不需要任何算法,但细节不容易处理。
首先不难想到的是O(n^2)的做法。我们将所有办公城市与居住城市从小到大排列,这样一来无论工作地点如何分配,其相对顺序都不会改变。因此只需要将第一个员工逐个尝试放置于不同城市中工作,再统计出其他员工的上班总距离即可。
考虑如何优化时间复杂度。我们令ans[i]表示第一个员工向右移动i个城市时的上班总距离,move(i, j)表示居住在第i个城市的员工假如要到第j个城市上班,则相当于向右移动了多少个城市,dis(x[i], y[j])表示第i个员工到第j个城市工作的上班距离,则题目要求的本质上是以下式子:
尝试分类讨论写出对于某一个i,dis(x[i], y[i])的表达式:
注意到这个表达式分为四段。分段点y[j]满足随着x[i]递增而递增,因此我们可以将从小到大遍历一遍,三个分段点也就从左到右移动一遍,每到达一个i,我们按照分段点得到的区间,对于一整段的j,在ans[move(i, j)]中整段加上\(a \cdot x[i] + b \cdot m\)(用前缀和维护)。至于剩下y[j]的项怎么处理?我们注意到,对于每一个y[i], dis(x[j], y[i])同样有一个分为四类的表达式,因此我们按照y[i]再遍历一遍,即可在ans中加完所有项。
必须要注意:在dis(x[i], y[j])的表达式中,可能存在x[i]和y[j]正好在圆圈的一条直径上的情况,即从任意一侧上班,距离都是相等的。这种情况下,在遍历x[i]和y[i]时统一表达式,否则就会出错。x[i]=y[j]的情况同理,要注意是哪个减哪个。
#include <cstdio>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
inline int getInt()
{
int a = 0, sgn = 1; char c;
while (! isdigit(c = getchar())) if (c == '-') sgn *= -1;
while (isdigit(c)) a = a * 10 + c - '0', c = getchar();
return a * sgn;
}
const int N = 10 + 2e5;
int m, n;
struct pr
{
int pos, id;
}x[N], y[N];
inline bool comp(pr a, pr b) {return a.pos < b.pos;}
long long pre[N]; //[i]表示往右移动i位
int op[N];
inline void modify1(int p, int L, int R, int a)
{
if (R < p) pre[L + n - p] += a, pre[R + n - p + 1] -= a;
else if (L < p && R >= p)
{
pre[L + n - p] += a; pre[n] -= a;
pre[0] += a; pre[R - p + 1] -= a;
}
else if (L >= p) pre[L - p] += a, pre[R - p + 1] -= a;
}
inline void modify2(int p, int L, int R, int a)
{
if (R <= p) swap(L, R), pre[p - L] += a, pre[p - R + 1] -= a;
else if (L <= p && R > p)
{
pre[0] += a; pre[p - L + 1] -= a;
pre[p + n - R] += a; pre[n] -= a;
}
else if (L > p) swap(L, R), pre[p + n - L] += a, pre[p + n - R + 1] -= a;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("F.in", "r", stdin);
freopen("F.out", "w", stdout);
#endif
m = getInt(); n = getInt();
for (int i = 1; i <= n; i ++) y[i].pos = getInt(), y[i].id = i;
for (int i = 1; i <= n; i ++) x[i].pos = getInt(), x[i].id = i;
sort(x + 1, x + n + 1, comp); sort(y + 1, y + n + 1, comp);
int p1 = 1, p2 = 1, p3 = 1;
memset(pre, 0, sizeof pre);
for (int i = 1; i <= n; i ++)
{
while (p1 <= n && (long long)2 * y[p1].pos < 2 * (long long)x[i].pos - m) p1 ++;
while (p2 <= n && y[p2].pos < x[i].pos) p2 ++;
while (p3 <= n && (long long)2 * y[p3].pos < 2 * (long long)x[i].pos + m) p3 ++;
if (p1 > 1) modify1(i, 1, p1 - 1, - x[i].pos + m);
if (p1 < p2) modify1(i, p1, p2 - 1, x[i].pos);
if (p2 < p3) modify1(i, p2, p3 - 1, -x[i].pos);
if (p3 <= n) modify1(i, p3, n, x[i].pos + m);
}
p1 = 1; p2 = 1; p3 = 1;
for (int i = 1; i <= n; i ++)
{
while (p1 <= n && (long long)2 * x[p1].pos <= 2 * (long long)y[i].pos - m) p1 ++;
while (p2 <= n && x[p2].pos <= y[i].pos) p2 ++;
while (p3 <= n && (long long)2 * x[p3].pos <= 2 * (long long)y[i].pos + m) p3 ++;
if (p1 > 1) modify2(i, 1, p1 - 1, -y[i].pos);
if (p1 < p2) modify2(i, p1, p2 - 1, y[i].pos);
if (p2 < p3) modify2(i, p2, p3 - 1, -y[i].pos);
if (p3 <= n) modify2(i, p3, n, y[i].pos);
}
long long ans = 1e15; int mv;
for (int i = 0; i < n; i ++)
{
pre[i] += pre[i - 1];
if (pre[i] < ans) ans = pre[i], mv = i;
}
printf("%lld\n", ans);
for (int i = 1; i <= n; i++)
if (i > mv) op[y[i].id] = x[i - mv].id; else op[y[i].id] = x[i + n - mv].id;
for (int i = 1; i <= n; i++) printf("%d ", op[i]);
}