[NOIP 2005] 篝火晚会:大胆猜想,小心求证

题意:N(N<=50000)个数1, 2, …, N围成一个圈,每次可以指定m个数,把b1放到b2的位置,把b2放到b3的位置,……把bm放到b1的位置,代价为m。要求每个数和指定的两个数相邻,求最小代价或报告方案不存在。

为什么要看题解呢?看到这题在CodeVS上分在大师级别,又听说要用到群论,题意还没搞明白就失去了信心TAT 如果全是自己想出来的就好了。

考虑简化问题,对于0, 1, …, N-1的一个排列,将它变换为另一个排列,问最小代价。
问题一定有解。如图所示:

[NOIP 2005] 篝火晚会:大胆猜想,小心求证_第1张图片

可以看出最小代价等于不在正确位置上的数字个数。

首先,不在正确位置上的数字个数是代价的一个下界,因为这些数必须换位置,要换位置就至少贡献1代价。这个下界是可以达到的。考虑第1个不在正确位置的数,它应该在哪里呢?1号位置。1号位置的数应该摆在哪里呢?8号位置。……5号位置的数应该摆在哪里呢?0号位置。构成一个环。现在对它们进行操作。复原。对剩下不在正确位置的数进行同样的处理。于是,如果这个算法正确,那么我们就证明了下界的可达性。

提问x的正确位置在哪里,只有一个确定的答案,于是每个点的出度为1。如果一个点不在正确位置,那么有且只有一个点应该替换掉它,于是每个点的入读为1。从而,我们证明了最小代价等于不在正确位置上的数字的个数。

给定每个数和指定两个数相邻,可以把环构造出来,如果存在。无法构造出一个环,表明无解。

将初始环0, 1, …, N变换为目标环等价于把目标环变换为初始环。可以把环断成链,和0, 1, ..., N-11, 2, ..., N-1, 0……还有N, N-1, ..., 00, N, ..., 2, 1……做比较取最少的不在正确位置上的数字个数即可。

这个算法的时间复杂度是 O(n2) 。能不能更优呢?

我有两个思路,一是在0, 1, ..., N-11, 2, ..., N-1, 0的结果之间建立联系,二是换一个角度计算。发现第二种是可行的。

做比较的时候我们实际上在干什么?对于x = 0, 1, …, N-1,统计有多少个 ai=(ix)modN ,有多少个 ai=(xi)modN ,取最大,用N去减。欣喜地发现,对于某个i,分别有唯一的x使之成立,于是,把贡献加到x上即可。

我觉得,难点不在于从30分到100分,而在于从0分到30分。面对一个全新的问题,怎样产生有益的想法呢?

#include 
#include 
using namespace std;
const int MAX_N = 50000;
int ch[MAX_N][2], d[MAX_N], l[MAX_N], r[MAX_N], t[2][MAX_N];

int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 0; i < n; ++i) {
        scanf("%d %d", &ch[i][0], &ch[i][1]);
        --ch[i][0];
        --ch[i][1];
        ++d[ch[i][0]];
        ++d[ch[i][1]];
    }

    for (int i = 0; i < n; ++i)
        if (d[i] != 2) {
            puts("-1");
            return 0;
        }

    l[0] = ch[0][0];
    r[0] = ch[0][1];
    int x = 0, cnt = 0;
    do {
        int y = r[x];
        l[y] = x;
        r[y] = ch[y][0] == x ? ch[y][1] : ch[y][0];
        x = y;
        ++cnt;
    } while (x != 0);

    if (cnt != n) {
        puts("-1");
        return 0;
    }

    for (int i = 0, x = 0; i < n; ++i, x = r[x])
        ++t[0][(i-x+n)%n];
    for (int i = 0, x = 0; i < n; ++i, x = l[x])
        ++t[1][(i-x+n)%n];

    int ans = 0;
    for (int i = 0; i < n; ++i)
        ans = max(ans, max(t[0][i], t[1][i]));

    printf("%d\n", n-ans);

    return 0;
}

你可能感兴趣的:(趣题)