题解NOIP2020 T3

引入

柱上球纷纷,色各自分散。
轻移终归位,步步不逾限。

题目描述

这个题目描述了一个球的移动游戏,要求通过一系列操作将不同颜色的球从多个柱子上搬移到同一个柱子上,最终使得每个颜色的球都集中在一个柱子上,并且需要控制操作次数不超过 820,000 次。任务目标是设计一种移动方案,使得每种颜色的球最终都位于同一根柱子上,同时满足题目给出的操作限制。

问题分析

1. 柱子和球的排列

我们有 n 个柱子,每个柱子上有 m 个球。球的颜色可以重复,每个柱子上的球从底部到顶部排列,球的颜色在每根柱子上都是按照顺序给出的。题目要求的是:通过一系列的合法操作将每种颜色的球移动到一个柱子上,最终每根柱子上仅包含同种颜色的球。

2. 操作要求

每次操作可以将一个球从一个柱子 x 移动到另一个柱子 y。操作的条件是:

  • x 栏上至少有一个球;
  • y 栏上最多可以有 m-1 个球;
  • 每次操作只能从 x 栏的最上面移动一个球到 y 栏的最上面。

3. 问题目标

  • 在不超过 820,000 次操作的情况下,将每种颜色的球移动到同一根柱子上。
  • 输出每次操作的详情:从哪个柱子移到哪个柱子。

思路和解决方案

为了完成这一任务,可以采用一种逐步合并的策略。具体的思路如下:

1. 构造一个空柱子

假设我们有一个额外的柱子(编号为 n+1),开始时这个柱子是空的。我们的策略是:首先将所有柱子上的球移动到这个空柱子上,然后再逐步将同一种颜色的球集中到同一个柱子上。

2. 处理每个颜色的球

对于每种颜色的球,我们可以依次从柱子上移动该颜色的球,直到所有该颜色的球都集中在同一柱子上。具体步骤如下:

  • 对于每种颜色的球,从柱子中挑选包含该颜色的球,将它们逐个移到一个空柱子 n+1
  • 然后,逐步将同种颜色的球集中到一个指定的柱子上。

3. 从右到左处理柱子

我们可以采用从右到左的方式处理每个柱子上的球。即,先处理最右侧的柱子,将球移动到空柱子中,然后依次往左移动。这样可以保证每个柱子上的球都能被合理处理,并且在处理时不会影响其他柱子上的球。

4. 操作计数

在每次操作中,我们需要记录从哪个柱子移动到哪个柱子的操作,并确保每次操作不超过 m-1 次。为了避免超过操作限制,我们在移动球时尽量减少无效的操作。

详细步骤

  1. 初始化数据结构

    • 读取柱子的数量 n 和每个柱子上的球的数量 m
    • 使用二维数组 ball[i][j] 表示第 i 个柱子上的第 j 个球。ball_count[i] 记录每个柱子上的球的数量。
  2. 逐步移动球

    • 从右向左处理每根柱子,将柱子上的球移到空柱子 n+1
    • 逐个处理每种颜色的球,确保将所有相同颜色的球移到一个指定的柱子上。
  3. 记录操作

    • 每次移动操作时,记录柱子的编号,并确保每个操作合法。
  4. 输出结果

    • 最后输出所有的操作次数和操作详情。

代码实现

#include<bits/stdc++.h>
using namespace std;

const int MAXN = 55;
const int MAXM = 405;
const int MAXO = 820005;

int read() {
    int x = 0;
    char c = getchar(), sign = '+';
    while (c < '0' || c > '9') {
        sign = c;
        c = getchar();
    }
    while (c >= '0' && c <= '9') {
        x = x * 10 + c - '0';
        c = getchar();
    }
    return sign == '-' ? -x : x;
}

void write(int x) {
    if (x < 0) {
        putchar('-');
        x = -x;
    }
    if (x < 10) putchar(x + '0');
    else {
        write(x / 10);
        putchar(x % 10 + '0');
    }
}

void output(int x = -1, char c = '\n') {
    write(x);
    putchar(c);
}

int L[MAXO], R[MAXO], move_count = 0, n, m;
int ball[MAXN][MAXM], ball_count[MAXN], tot[MAXN], pos[MAXN];

void moveBall(int x, int y) {
    ++move_count;
    L[move_count] = x;
    R[move_count] = y;
    ball[y][++ball_count[y]] = ball[x][ball_count[x]--];
}

int countColor(int x, int color) {
    int result = 0;
    for (int i = 1; i <= m; ++i)
        result += ball[x][i] == color;
    return result;
}

inline int topBall(int x) {
    return ball[x][ball_count[x]];
}

int main() {
    n = read();
    m = read();
    for (int i = 1; i <= n; ++i) {
        ball_count[i] = m;
        for (int j = 1; j <= m; ++j)
            ball[i][j] = read();
    }
    ball_count[n + 1] = 0;
    for (int i = 1; i <= n + 1; ++i)
        pos[i] = i;
    for (int now = n; now >= 3; --now) {
        int tmp = countColor(pos[1], now);
        for (int i = 1; i <= tmp; ++i)
            moveBall(pos[now], pos[now + 1]);

        for (int i = 1; i <= m; ++i)
            if (topBall(pos[1]) == now) moveBall(pos[1], pos[now]);
            else moveBall(pos[1], pos[now + 1]);

        for (int i = 1; i <= m - tmp; ++i)
            moveBall(pos[now + 1], pos[1]);

        for (int i = 1; i <= m; ++i)
            if (topBall(pos[2]) == now || ball_count[pos[1]] == m)
                moveBall(pos[2], pos[now + 1]);
            else
                moveBall(pos[2], pos[1]);

        swap(pos[1], pos[now]);
        swap(pos[2], pos[now + 1]);

        for (int k = 1; k < now; ++k) {
            tmp = countColor(pos[k], now);
            for (int i = 1; i <= tmp; ++i)
                moveBall(pos[now], pos[now + 1]);

            for (int i = 1; i <= m; ++i)
                if (topBall(pos[k]) == now) moveBall(pos[k], pos[now]);
                else moveBall(pos[k], pos[now + 1]);

            swap(pos[k], pos[now + 1]);
            swap(pos[k], pos[now]);
        }

        for (int i = 1; i < now; ++i)
            while (topBall(pos[i]) == now) moveBall(pos[i], pos[now + 1]);

        for (int i = 1; i < now; ++i)
            while (ball_count[pos[i]] < m) moveBall(pos[now], pos[i]);
    }
    int tmp = countColor(pos[1], 1);
    for (int i = 1; i <= tmp; ++i)
        moveBall(pos[2], pos[3]);

    for (int i = 1; i <= m; ++i)
        if (topBall(pos[1]) == 1) moveBall(pos[1], pos[2]);
        else moveBall(pos[1], pos[3]);

    for (int i = 1; i <= tmp; ++i)
        moveBall(pos[2], pos[1]);

    for (int i = 1; i <= m - tmp; ++i)
        moveBall(pos[3], pos[1]);

    while (ball_count[pos[3]])
        moveBall(pos[3], pos[2]);

    for (int i = 1; i <= m - tmp; ++i)
        moveBall(pos[1], pos[3]);

    for (

int i = 1; i <= m; ++i)
        if (topBall(pos[2]) == 1) moveBall(pos[2], pos[1]);
        else moveBall(pos[2], pos[3]);
    output(move_count);
    for (int i = 1; i <= move_count; ++i) {
        output(L[i], ' ');
        output(R[i]);
    }

    return 0;
}

解释与优化

  1. 时间复杂度

    • 由于每次操作的目标是将每根柱子上的球通过逐步移动完成任务,操作的总次数被限制在 820000 次以内,因此我们的设计避免了不必要的重复操作,确保了时间复杂度足够高效。
  2. 操作记录与输出

    • 每次球的移动都会通过 LR 数组记录,并最终输出到标准输出中,确保每次操作的详情符合题目要求。
  3. 思路简述

    • 通过模拟球的移动过程,并依照颜色进行逐步聚集,结合合理的柱子排序策略,确保所有球都最终按颜色聚集在目标柱子上。
posted @ 2024-11-12 14:57  健康铀  阅读(6)  评论(0编辑  收藏  举报