[SMOJ2083]篝火晚会

97 篇文章 0 订阅
9 篇文章 0 订阅

首先考虑无解的情况。显然,如果目标环存在,则一定有解。否则无解,可以进行一次遍历,如果能够正好经过 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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值