Astral Superposition

GitHub跳转原题关系图返回列表

先强制满足黑色像素,再检查白色冲突,并用贪心为未满足的灰色像素补星星。

OJ: usaco

题目 ID: 1467

难度:普及-

标签:贪心模拟

日期: 2026-07-11 12:25

题意

有一张星空照片,初始时某些格子有星星。

一夜之后,每颗星星要么消失,要么向右移动 AA、向下移动 BB。如果移出照片范围,就不会出现在第二张照片中。

现在把第一张照片和第二张照片叠加,得到一张颜色图:

  • W:两张照片这个位置都没有星星;
  • G:两张照片恰好一张有星星;
  • B:两张照片这个位置都有星星。

要求判断这张叠加图是否可能出现。如果可能,求初始照片中星星数量的最小值。

思路

暴力想法

最直接的想法是枚举初始照片中每个格子有没有星星。

枚举出一张初始照片后,再逐格判断它能否通过“消失或移动”解释最终颜色。

下面的暴力把每个格子看成一个 01 选择:choose_star[i]=1 表示这个格子初始有星星。它只适合小数据,但适合用来理解颜色约束和对拍。

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-11 12:25
 * update_at: 2026-07-11 12:27
 */
// brute.cpp:小数据暴力解,用来帮助理解题意并辅助对拍。
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 6;
const int MAXC = 30;

int n, a, b;
int total_cell;
char color[MAXN][MAXN];
int choose_star[MAXC]; // choose_star[k]=1 表示第 k 个格子在初始照片中有星星
bool first_photo[MAXN][MAXN];
int best;

void cell_pos(int id, int &r, int &c) {
    r = (id - 1) / n + 1;
    c = (id - 1) % n + 1;
}

bool has_source(int r, int c) {
    return r - b >= 1 && c - a >= 1;
}

bool check_choice() {
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            first_photo[i][j] = false;
        }
    }

    for (int id = 1; id <= total_cell; id++) {
        int r, c;
        cell_pos(id, r, c);
        if (choose_star[id] == 1) {
            first_photo[r][c] = true;
        }
    }

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            bool first = first_photo[i][j];
            bool need_second = false;

            if (color[i][j] == 'W') {
                if (first) {
                    return false;
                }
                need_second = false;
            } else if (color[i][j] == 'G') {
                // 灰色表示前后两张照片恰好一张有星星。
                need_second = !first;
            } else {
                if (!first) {
                    return false;
                }
                need_second = true;
            }

            if (need_second) {
                if (!has_source(i, j)) {
                    return false;
                }
                if (!first_photo[i - b][j - a]) {
                    return false;
                }
            }
        }
    }

    return true;
}

void dfs_choose(int dep, int cnt) {
    if (cnt >= best) {
        return;
    }
    if (dep == total_cell + 1) {
        if (check_choice()) {
            best = cnt;
        }
        return;
    }

    // 这一层选择当前格子初始时有没有星星。
    choose_star[dep] = 0;
    dfs_choose(dep + 1, cnt);

    choose_star[dep] = 1;
    dfs_choose(dep + 1, cnt + 1);
}

int solve_one() {
    cin >> n >> a >> b;
    for (int i = 1; i <= n; i++) {
        string s;
        cin >> s;
        for (int j = 1; j <= n; j++) {
            color[i][j] = s[j - 1];
        }
    }

    total_cell = n * n;
    best = total_cell + 1;
    dfs_choose(1, 0);

    if (best == total_cell + 1) {
        return -1;
    }
    return best;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;
    while (t--) {
        cout << solve_one() << '\n';
    }

    return 0;
}

暴力的瓶颈是初始照片有 N2N^2 个格子,需要枚举 2N22^{N^2} 种星星集合。

两遍扫描

对于位置 (r,c),第二张照片中出现在这里的星星,只能来自:

text
(r-B, c-A)

如果这个来源位置越界,就不可能有星星移动到 (r,c)

先看黑色像素 B。它表示两张照片在当前位置都有星星,所以:

text
has_star[r][c] = true
has_star[r-B][c-A] = true

如果来源越界,直接无解。

处理完所有黑色像素后,再扫描白色和灰色:

  • W:当前位置不能有初始星星,如果 has_star[r][c] 已经为真,则无解;
  • G:如果当前位置已有初始星星,可以让它消失,当前像素就是灰色;
  • G:否则如果来源位置已有初始星星,可以让它移动过来,当前像素也是灰色;
  • G:如果两者都没有,就必须新增一颗星星。把它放在当前位置最优,因为它还能作为后续位置的来源。

这个贪心依赖扫描顺序:按行从上到下、每行从左到右扫描时,(r-B,c-A) 一定不会晚于 (r,c),所以来源位置是否有星星已经确定。

最后统计 has_star 中的星星数量。

灰色格子的贪心本质

这题真正容易想错的地方在灰色格子。

灰色 G 的意思是:当前位置和来源位置中,恰好有一个能解释这个像素。把它写成逻辑关系,就是:

has_star(r,c)has_star(rB,cA) has\_star(r,c) \oplus has\_star(r-B,c-A)

从人脑思考的角度,可以把每个灰色格子看成一个需求:

text
当前位置有星星,或者来源位置有星星,至少要满足一个。

为了让初始星星数量最少,能不新增就不新增:

  • 如果当前位置已经有星星,这个灰色格子已经满足;
  • 否则如果来源位置已经有星星,也可以借来源星星移动过来;
  • 如果两边都没有,就必须新增一颗星星。

关键是最后一种情况:为什么新增时放在当前位置,而不是放在来源位置?

因为扫描到 (r,c) 时,来源位置 (r-B,c-A) 已经在扫描顺序中过去了。现在再把星星补到来源位置,只会影响已经处理过的格子,可能制造新的冲突;而放在当前位置,不仅能满足当前灰色格子,还可能作为后面格子的来源。

所以灰色格子的贪心可以记成一句话:

text
能借前面的星星就借;借不到,就在当前位置放一颗,因为当前位置还能帮后面。

这也是本题的本质:在一个由固定偏移形成的先后依赖关系上,按顺序做最少补点。

代码

cpp
/**
 * Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
 * rbook: -> https://rbook.roj.ac.cn  https://rbook2.roj.ac.cn
 * rainboy的学习导航网站: https://idx.roj.ac.cn
 * create_at: 2026-07-11 12:25
 * update_at: 2026-07-11 12:27
 */
#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1005;

int n, a, b;
char color[MAXN][MAXN];
bool has_star[MAXN][MAXN]; // has_star[i][j] 表示初始照片中这个位置有星星

void clear_case() {
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            has_star[i][j] = false;
        }
    }
}

bool has_source(int r, int c) {
    return r - b >= 1 && c - a >= 1;
}

int solve_one() {
    cin >> n >> a >> b;
    clear_case();

    for (int i = 1; i <= n; i++) {
        string s;
        cin >> s;
        for (int j = 1; j <= n; j++) {
            color[i][j] = s[j - 1];
        }
    }

    bool ok = true;

    // 黑色像素表示前后两张照片这里都有星星。
    // 因此当前位置必须有初始星星,且它的来源位置也必须有初始星星并移动过来。
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (color[i][j] == 'B') {
                has_star[i][j] = true;
                if (!has_source(i, j)) {
                    ok = false;
                } else {
                    has_star[i - b][j - a] = true;
                }
            }
        }
    }

    if (!ok) {
        return -1;
    }

    // 白色不能有初始星星;灰色必须恰好由当前位置或来源位置贡献一颗星星。
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (color[i][j] == 'W') {
                if (has_star[i][j]) {
                    return -1;
                }
            } else if (color[i][j] == 'G') {
                if (has_star[i][j]) {
                    continue;
                }
                if (has_source(i, j) && has_star[i - b][j - a]) {
                    continue;
                }
                has_star[i][j] = true;
            }
        }
    }

    int ans = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            if (has_star[i][j]) {
                ans++;
            }
        }
    }
    return ans;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int t;
    cin >> t;
    while (t--) {
        cout << solve_one() << '\n';
    }

    return 0;
}

复杂度

每组数据只扫描常数遍网格,时间复杂度 O(N2)O(N^2)

空间复杂度 O(N2)O(N^2)

由于题目保证所有测试用例的 N2N^2 之和不超过 10710^7,这个复杂度可以通过。

总结

这题的关键是把叠加颜色拆成“第一张照片是否有星星”和“第二张照片是否有星星”。

黑色像素给出强制条件,白色像素负责发现冲突,灰色像素再用贪心补齐。 固定偏移让每个位置只有唯一来源,所以两遍扫描就能完成构造和判定。