[SMOJ2083]篝火晚会

来源:互联网 发布:网络购物流程 编辑:程序博客网 时间:2024/04/28 20:34

首先考虑无解的情况。显然,如果目标环存在,则一定有解。否则无解,可以进行一次遍历,如果能够正好经过 N 个点,则有解,否则无解。

接下来,就可以根据所构造的环,考虑花费。来看下面这个例子(为了方便,破成链表示):
原环:1 2 3 4
目标:3 2 4 1

显然,2 已经在目标位置,如果将其移动,肯定会没有必要的额外花费。考虑 1,它要去向 4 的位置,因此 b 数组为 {1, 4},而当 4 的位置被占了之后,它要去向 3 的位置,因此 b 为 {1, 4, 3},3 要去向 1 的位置,发现形成了一个环,调整结束,费用为 b 数组的大小(同时也是环的长度),即 3。

可以发现,每个人到达目标位置的代价都是 1,而且到达之后不再移动,而是把原来所在的那个人“挤走”,让他自己找位置去。一轮重复下来可以发现,其实不在原位人数就是费用。但是要注意题目是一个环,上面只是为了方便表示,破成了一种可能的链。事实上,形如 2 4 1 3 或者 4 1 3 2 也是可能的。
这样就要通过枚举开头破环,扫一遍算花费,取个 min 就行了。需要注意的是,环的方向可以有正反两种,都要考虑。时间复杂度 O(n2)。这是 30% 的做法。

上面的思路,是直接算“不在位人数”作为花费。如果逆向思考,发现“n-在位人数”的方法也应该是可行的。要最小化花费,应该找到一个合适的匹配位置,使在位人数尽可能多。幸运的是,可以发现,对于环中的每个人,能使其保持不动的开头有且只有一种。
例如:(target 是把环展开了,有 4 种可能的开头,最后一个 1 打多了)

简单地推一下就可以发现,记 ci 为目标环中第 i 位的人,那么把开头放在 cii 处时,当前的人不用移动。

这样,就可以考虑计算对于最终环中的每个人,将开头放在什么位置可以使其不用移动。最后在所有情况中选择不用移动的人最多的,用 n 减去,就是答案。同样注意需要考虑正反两种情况。
时间复杂度为 O(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;}


原创粉丝点击