首先考虑无解的情况。显然,如果目标环存在,则一定有解。否则无解,可以进行一次遍历,如果能够正好经过 N 个点,则有解,否则无解。
接下来,就可以根据所构造的环,考虑花费。来看下面这个例子(为了方便,破成链表示):
原环:1 2 3 4
目标:3 2 4 1
显然,2 已经在目标位置,如果将其移动,肯定会没有必要的额外花费。考虑 1,它要去向 4 的位置,因此
可以发现,每个人到达目标位置的代价都是 1,而且到达之后不再移动,而是把原来所在的那个人“挤走”,让他自己找位置去。一轮重复下来可以发现,其实不在原位人数就是费用。但是要注意题目是一个环,上面只是为了方便表示,破成了一种可能的链。事实上,形如 2 4 1 3 或者 4 1 3 2 也是可能的。
这样就要通过枚举开头破环,扫一遍算花费,取个 min 就行了。需要注意的是,环的方向可以有正反两种,都要考虑。时间复杂度
O(n2)
。这是 30% 的做法。
上面的思路,是直接算“不在位人数”作为花费。如果逆向思考,发现“
n
-在位人数”的方法也应该是可行的。要最小化花费,应该找到一个合适的匹配位置,使在位人数尽可能多。幸运的是,可以发现,对于环中的每个人,能使其保持不动的开头有且只有一种。
例如:(target 是把环展开了,有 4 种可能的开头,最后一个 1 打多了)
简单地推一下就可以发现,记
这样,就可以考虑计算对于最终环中的每个人,将开头放在什么位置可以使其不用移动。最后在所有情况中选择不用移动的人最多的,用
n
减去,就是答案。同样注意需要考虑正反两种情况。
时间复杂度为
这题知道了正解之后看似不难(然而比赛的时候我还是没想出来),不过仔细品味一下,其实里面有一种方法还是很重要的,即通过枚举来破环。往往我们对于一些无法直接解决的限制,可以考虑利用枚举来突破,有时可以取得很好的效果。
参考代码:
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;
const int MAXN = 5e4 + 100;
int n;
int wishcnt[MAXN], wish1[MAXN][2], wish2[MAXN][2]; //被多少人希望,自己希望谁,谁希望自己
bool init() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%d%d", &wish1[i][0], &wish1[i][1]);
if (wishcnt[wish1[i][0]] == 2 || wishcnt[wish1[i][1]] == 2) return false; //不可能有超过 2 个人同时希望与一个人坐
wish2[wish1[i][0]][wishcnt[wish1[i][0]]++] = wish2[wish1[i][1]][wishcnt[wish1[i][1]]++] = i;
for (int j = 0; j < 2; j++) { //自己希望的人是否希望自己
if (wish1[i][j] > i) continue;
bool ok = false;
for (int k = 0; k < 2; k++)
if (wish1[wish1[i][j]][k] == i) ok = true;
if (!ok) return false;
}
for (int j = 0; j < wishcnt[i]; j++) //希望自己的人是否被自己所希望
if (wish2[i][j] != wish1[i][0] && wish2[i][j] != wish1[i][1]) return false;
}
return true;
}
int circle[MAXN], start[MAXN][2];
bool vis[MAXN];
int solve() {
if (!init()) return -1;
circle[1] = 1; circle[2] = wish1[1][0]; vis[circle[1]] = vis[circle[2]] = true;
//circle 为调整后的环
for (int i = 2; i < n; i++) {
if (circle[i - 1] == wish1[circle[i]][0]) circle[i + 1] = wish1[circle[i]][1];
else if (circle[i - 1] == wish1[circle[i]][1]) circle[i + 1] = wish1[circle[i]][0];
else return -1;
vis[circle[i + 1]] = true;
}
for (int i = 1; i <= n; i++) if (!vis[i]) return -1;
int ans1 = 0, ans2 = 0;
for (int i = 1; i <= n; i++) {
ans1 = max(ans1, ++start[(circle[i] - i + n) % n][0]); //正
ans2 = max(ans2, ++start[(circle[n - i + 1] - i + n) % n][1]); //反
}
return n - max(ans1, ans2);
}
int main(void) {
freopen("2083.in", "r", stdin);
freopen("2083.out", "w", stdout);
printf("%d\n", solve());
return 0;
}