P7668 [JOI 2018 Final] 团子制作 / Dango Maker 题解

P7668 JOI 2018 Final 团子制作 / Dango Maker 题解

复盘

赛时写的二维DP,转移非常复杂,而且正确性得不到保证

赛后发现题解非常短小,对于这个贪心的性质分析感到十分巧秒,特此记录

题意简述

在 \(N \times M\) 的字符网格上,每个格子放着一个颜色为 RGW 的团子。每次选三个连续格子(横向或竖向),颜色必须恰好为 R G W,且每个格子只能被使用一次。求最多能串多少串团子。

核心分析

关键观察:冲突构型

一根横向串和一根竖向串如果要共用某个格子,几何上只有三种重叠方式:

复制代码
构型 A(共享 G 格)    构型 B(共享 R 格)    构型 C(共享 W 格)
R G W                  R                      R
  G                    R G W                  G
  W                    W                    R G W

逐一检验这三种构型能否实际发生:

构型 共享格在横向串中的角色 共享格在竖向串中的角色 是否可能
A G(中心) R(顶端) 不可能,一个格子不可能既是 G 又是 R
B R(左端) G(中心) 不可能,一个格子不可能既是 R 又是 G
C W(右端) W(底端) 可能,两边都要求 W

结论:横向串与竖向串的冲突只可能在构型 C 下发生。

冲突的位置关系

以每根串的 G 格作为标志点(一根串由 G 的位置唯一确定),分析构型 C 中两个 G 点的位置关系:

复制代码
       j        j+1
i-1            G'  ← 竖向串(向下:R→G'→W)
i      G  ←───── 横向串(向右:R→G→W)
  • 竖向串标志点 \(G'\) 在 \((i-1,; j+1)\),使用格子 \((i,; j+1)\) 作为 W。

  • 横向串标志点 \(G\) 在 \((i,; j)\),也使用格子 \((i,; j+1)\) 作为 W。

两个 G 点的相对位置恰好是「右上方」关系 :\(G' = (i-1,; j+1)\),\(G = (i,; j)\)。

这意味着:处理 \(G(i,j)\) 时,只需检查它的右上方邻居 \(G(i-1,; j+1)\) 是否与之冲突,其余位置不会产生冲突。

算法设计

DP 状态

\f\\_{i,j,0/1} = \\text{以 } (i,j) \\text{ 为中心的 G 能否形成横向(0)/竖向(1) 串} \\

其中 \(f\{i,j,0} = 1\) 当且仅当 \(s\{i,j-1} = \text{R}\) 且 \(s\_{i,j+1} = \text{W}\),竖向同理。

贪心转移

按行优先序扫描。处理 \(G(i,j)\) 时,右上方 \(G(i-1,; j+1)\) 已处理完毕。

冲突规则(由构型 C 导出):

右上方 G' 的选择 占用的冲突格 当前 G 被禁止的方向
横向 \((i-1, j)\) 作为 R 竖向(竖向也需要 \((i-1, j)\) 作为 R)
竖向 \((i, j+1)\) 作为 W 横向(横向也需要 \((i, j+1)\) 作为 W)

核心逻辑:如果 G' 只能走某个方向,就被迫占用冲突格,从而禁止 G 的对应方向。如果 G' 两个方向都可行,它有弹性去适应 G 的需求,因此不限制 G。

代码实现为:

cpp 复制代码
if (f[i-1][j+1][0] || f[i-1][j+1][1]) {   // G' 存在且至少有一个方向可行
    f[i][j][0] &= f[i-1][j+1][0];           // G' 不能横向 → G' 被迫竖向 → 禁止当前横向
    f[i][j][1] &= f[i-1][j+1][1];           // G' 不能竖向 → G' 被迫横向 → 禁止当前竖向
}
ans += (f[i][j][0] || f[i][j][1]);           // 只要还有一个可行方向就算一根

贪心正确性

冲突关系只在反斜对角线 (\(i+j = \text{const}\))上相邻的 G 点之间传递,冲突图是若干条(而非一般图)。

在链上做贪心是正确的:

  • \(G'\) 能两个方向都走 \(\Rightarrow\) 它可以迁就 \(G\) 的任何选择,不损失任何一根。

  • \(G'\) 只能走一个方向 \(\Rightarrow\) 被迫占用冲突格,\(G\) 对应方向必然失效,无可选择。

  • \(G'\) 两个方向都不行 \(\Rightarrow\) 无冲突,\(G\) 不受影响。

每一步的局部最优就是全局最优,因为每条链上的决策互不影响。

AC code

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

const int N = 3e3 + 5;

int n, m;
char s[N][N];
int f[N][N][2];

int main() {
    freopen("dango.in", "r", stdin);
    freopen("dango.out", "w", stdout);

    ios::sync_with_stdio(false);
    cin.tie(0);

    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= m; j++)
            cin >> s[i][j];

    int ans = 0;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (s[i][j] == 'G') {
                // 判断横向/竖向是否基本可行
                f[i][j][0] = (s[i][j-1] == 'R' && s[i][j+1] == 'W');
                f[i][j][1] = (s[i-1][j] == 'R' && s[i+1][j] == 'W');

                // 与右上方 G' 的冲突处理
                if (f[i-1][j+1][0] || f[i-1][j+1][1]) {
                    f[i][j][0] &= f[i-1][j+1][0];
                    f[i][j][1] &= f[i-1][j+1][1];
                }

                ans += (f[i][j][0] || f[i][j][1]);
            }
        }
    }

    cout << ans << '\n';
    return 0;
}

复杂度

  • 时间:\(O(NM)\),每个格子常数次操作。

  • 空间:\(O(NM)\),f 数组。

总结

步骤 要点
1. 分析冲突构型 横竖串重叠只有 3 种几何构型,其中仅「共享 W 格」一种可行
2. 确定冲突位置关系 冲突只发生在 \(G(i,j)\) 与右上方 \(G(i-1,; j+1)\) 之间
3. 贪心/DP 转移 行优先扫描;G' 有弹性则不限制,G' 被迫单方向则禁止对应方向
4. 正确性 冲突图是反斜对角线上的链,链上贪心即全局最优
相关推荐
伟大的车尔尼10 小时前
贪心题目:玩筹码
贪心
奶人五毛拉人一块11 小时前
动态规划--子数组类型
算法·动态规划·子数组问题
lvwangshu1 天前
P6534 [COCI 2015/2016 #1] UZASTOPNI 等差树列 题解
动态规划·图论·题解·性质题
伟大的车尔尼1 天前
贪心题目:数组大小减半
贪心
漂流瓶jz1 天前
UVA-1609 不公平竞赛 题解答案代码 算法竞赛入门经典第二版
数据结构·算法·链表·贪心·aoapc·算法竞赛入门经典·uva
小玮看世界2 天前
[Python]动态规划三步走:从爬楼梯到打家劫舍,再到最大子数组和
开发语言·python·动态规划
Tisfy2 天前
LeetCode 2058.找出临界点之间的最小和最大距离:遍历+遇到极值则更新(这种题谁空间复杂度不是O(1)啊)
linux·数据库·leetcode·链表·题解·模拟·遍历
伟大的车尔尼4 天前
贪心的概念
数据结构·算法·贪心
吞下星星的少年·-·5 天前
The 2025 ICPC Asia East Continent Online Contest (I) A题(模拟+贪心)
算法·贪心·模拟