0题目原文(本地存了一份)
原题在洛谷上(页头有链接)。别人的网站不归我们管,打不开、改版、题号调整都可能发生。 所以每个解析页都把题面转录一份存在本地,跟着仓库一起进版本库。
转录自洛谷 P4170,日期见页头。两边不一致时信原站。
题目描述
假设你有一条长度为 n 的木板,初始时没有涂过任何颜色。
你希望把它的 n 个单位长度涂上目标颜色,用一个长度为 n 的字符串表示这个目标。
每次你可以把一段连续的木板涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色。
例如,对于目标字符串 RGBGR,一种涂色方式为:第一次把木板涂成 RRRRR,
第二次涂成 RGGGR,第三次涂成 RGBGR,达到目标。
用尽量少的涂色次数达到目标。
输入格式
输入仅一行,包含一个长度为 n 的字符串,即涂色目标。
字符串中的每个字符都是一个大写字母,不同的字母代表不同颜色,相同的字母代表相同颜色。
输出格式
仅一行,包含一个数,即最少的涂色次数。
说明/提示
40% 的数据满足 1 ≤ n ≤ 10。
100% 的数据满足 1 ≤ n ≤ 50。
输入输出样例(一)
输入
AAAAA
输出
1
五格全是 A —— 一刷刷完,1 次。
★ 这一组样例把本页两个错法全挡住了(没特判打 5、特判写错打 3)。
输入输出样例(二)
输入
RGBGR
输出
3
RGBGR:先整条 R,再中间 G,最后中间 B ⇒ 3 次。
⚠ 而这一组只挡住了「没特判」那一个(打 5)—— 「特判写错」那一版在这儿照样打 3。 ⇒ 第 16 章 P1074 那条:官方给了几组就跑几组,它们不是同一件事的重复。
1第一版:照石子合并的手感,纯按区间划分
// ✗ P4170 的第一版:**没有那个特判**,老老实实按区间划分。//// f[l][r] = min over k of ( f[l][k] + f[k+1][r] )//// 这是从[石子合并](/sol/p1775/)那儿原样搬过来的手感 —— 但它漏掉了这道题的全部门道:// **一刷可以跨过中间那些「后来会被盖掉」的格子**,所以两端同色时不该各算一次。//// ★ 方向能先说死:它只是**少了一种更省的拼法** ⇒ 答案**恒 ≥ 正解**。// ★ 官方那两组样例**都挡住了它**(AAAAA 打 5、RGBGR 打 5)——// 因为它其实退化成了「一格一刷」:答案恒等于长度 n。页面上把这条也钉成了断言。
#include <bits/stdc++.h>using namespace std;
int main() { char buf[64]; if (scanf("%63s", buf) != 1) return 0; string s = buf; int n = (int)s.size(); vector<vector<int>> f(n + 2, vector<int>(n + 2, 0)); for (int i = 1; i <= n; i++) f[i][i] = 1; for (int len = 2; len <= n; len++) for (int l = 1; l + len - 1 <= n; l++) { int r = l + len - 1; int best = INT_MAX; for (int k = l; k < r; k++) best = min(best, f[l][k] + f[k + 1][r]); f[l][r] = best; // ✗ 没有「两端同色」那一条 } printf("%d\n", f[1][n]); return 0;}点「运行 ▶」看结果
300 轮:它的答案 = 串长 n |
★ 300 / 300 |
| 它 ≥ 正解 | 300 / 300 |
| 被抓 | 202 / 300 |
⇒ 因为纯区间划分里,任何一刷都不能跨过分界线 —— 于是拆到最后只能一格一刷。说清楚它算了什么,它的所有表现就都是推论。
2★★ 关键的一步:两端颜色相同时,可以省下一次
s[l] == s[r] ⇒ f[l][r] = min( f[l+1][r], f[l][r−1] )
否则 ⇒ f[l][r] = min over k of ( f[l][k] + f[k+1][r] )为什么第一行成立:s[l] == s[r] 时,总存在一个最优方案让这两格
在同一次涂色里被盖住 —— 把盖住 l 的那一刷向右延到 r 不会变差,
因为后涂的会覆盖先涂的,多刷到的地方都会被后来的盖掉。
⇒ 那一刷既服务了 l 也服务了 r,代价只算一次;
剩下的问题就是「把 l(或 r)当成免费搭上的那一格」。
// P4170 [CQOI2007] 涂色 —— ★ 这一版就能 AC。//// f[l][r] = 把 [l, r] 这一段涂成目标的样子,最少要涂几次。//// ★★ 关键的一步:**两端颜色相同时,可以省下一次。**//// s[l] == s[r] ⇒ f[l][r] = min( f[l+1][r], f[l][r-1] )// 否则 ⇒ f[l][r] = min over k of ( f[l][k] + f[k+1][r] )//// 为什么第一行成立:`s[l] == s[r]` 时,总存在一个最优方案,// 让这两格**在同一次涂色里被盖住**(把盖住 l 的那一刷向右延到 r 不会变差 ——// 后面涂的会覆盖先涂的,多刷到的地方都会被后来的盖掉)。// ⇒ 于是「这一刷」既服务了 l 也服务了 r,代价只算一次,// 剩下的就是把 l(或 r)当成已经免费搭上的那一格。//// ⚠ 注意不能写成 `f[l+1][r-1] + 1`(见 p4170Inner.cpp):那等于**强行规定**// 这一刷只从 l 刷到 r 一整段、里面的事全部另算,会白花次数。//// 规模:`n ≤ 50` ⇒ 转移不到 2 万次,随便跑。
#include <bits/stdc++.h>using namespace std;
int main() { char buf[64]; if (scanf("%63s", buf) != 1) return 0; string s = buf; int n = (int)s.size(); vector<vector<int>> f(n + 2, vector<int>(n + 2, 0)); for (int i = 1; i <= n; i++) f[i][i] = 1;
for (int len = 2; len <= n; len++) for (int l = 1; l + len - 1 <= n; l++) { int r = l + len - 1; if (s[l - 1] == s[r - 1]) { f[l][r] = min(f[l + 1][r], f[l][r - 1]); // ★ 两端同色,省一次 } else { int best = INT_MAX; for (int k = l; k < r; k++) best = min(best, f[l][k] + f[k + 1][r]); f[l][r] = best; } } printf("%d\n", f[1][n]); return 0;}点「运行 ▶」看结果
3★★ 那个特判最容易写错的样子:`f[l+1][r-1] + 1`
「两端同色,那就先把 [l, r] 整段刷成这个颜色,再去解决中间」—— 听着很顺,
而且它是一个合法的方案,所以答案恒 ≥ 正解。
// ✗ P4170 的第二版:特判写成了 `f[l+1][r-1] + 1`。//// 「两端同色,那就先把 [l, r] 整段刷成这个颜色,再去解决中间」—— 听着很顺,// 而且它**是一个合法的方案**,所以答案恒 ≥ 正解。//// ⚠ 它错在**把那一刷的范围钉死了**。正确的写法 `min(f[l+1][r], f[l][r-1])` 说的是:// 那一刷只要**盖住 l 和 r**就行,中间刷到哪儿、刷了几段,都由子问题自己安排。//// ★ 最小的反例只要三个字符:`AAA` —— 正解 1(一刷刷完),而这一版算成 **2**。// (`f[2][2] + 1 = 2`:它非要「先刷整段、再单独处理中间那一格」。)// ⇒ 官方两组样例都挡住了它(AAAAA 打 3、RGBGR 打 3)。
#include <bits/stdc++.h>using namespace std;
int main() { char buf[64]; if (scanf("%63s", buf) != 1) return 0; string s = buf; int n = (int)s.size(); vector<vector<int>> f(n + 2, vector<int>(n + 2, 0)); for (int i = 1; i <= n; i++) f[i][i] = 1; for (int len = 2; len <= n; len++) for (int l = 1; l + len - 1 <= n; l++) { int r = l + len - 1; int best = INT_MAX; for (int k = l; k < r; k++) best = min(best, f[l][k] + f[k + 1][r]); if (s[l - 1] == s[r - 1]) best = min(best, f[l + 1][r - 1] + 1); // ✗ 钉死了那一刷 f[l][r] = best; } printf("%d\n", f[1][n]); return 0;}点「运行 ▶」看结果
| 最小反例 | ★ AAA —— 正解 1,它算成 2 |
| 它 ≥ 正解 | 300 / 300 |
| 300 轮被抓 | 67 |
⚠ 它错在把那一刷的范围钉死了。正确写法 min(f[l+1][r], f[l][r−1]) 说的是:
那一刷只要盖住 l 和 r 就行 —— 中间刷到哪儿、刷了几段,由子问题自己安排。
4★★ 参照物不是另一份 DP,是在「木板状态」这张图上跑 BFS
起点是「整块木板都没涂过」,一步 = 选一段区间 + 一个颜色刷下去,终点是目标串。 BFS 的层数就是最少涂色次数 —— 这条路和区间 DP 一个字都不共享。
| 300 轮:正解 vs 状态图 BFS | ★ 不一致 0 轮 |
⚠ 状态数是 (颜色数+1)ⁿ,所以只能用在很小的数据上(生成器压到 n ≤ 6、3 种颜色 ⇒ 至多 4⁶ = 4096 个状态)。
★ 而「小」在这道题上不是凑合:n = 6、三色已经足够造出 AABAA 这种
「两端同色、中间夹一段」的形状 —— 那正是这道题全部的门道
(第 6 章 P1719 那条:小本身就是覆盖能力)。
★ 和第 15 章 P1379 八数码 是同一个套路。
5★★ 一条可以自己验的性质:把连续相同的字符压成一个,答案不变
300 轮(n ≤ 30):压缩前后答案相同 |
★ 300 / 300 |
| 其中真的被压短了的 | 270 / 300 |
⇒ 因为「一段连续的同色」只可能被同一刷盖出来,多出来的那几格既不增加也不减少工作量。
★ 这条性质不是为了优化(n ≤ 50 根本不需要),它的价值是:
给正解找一条和它自己无关的自检(和第 11 章 P1966 那条同一个动作)。
6★ 规模:这道题的门槛完全不在复杂度上
顶格 n = 50 ⇒ 转移 |
20 225 次 |
40% 那一档 n ≤ 10 |
出题人给的暴力档(BFS 参照物也就能跑到这个规模) |
| ★ 平均答案随颜色数涨 | 2 色 7,3 色 9,8 色 15,26 色 19(n ≤ 50,各 100 组) |
7度量程序和生成器
8一页纸
| ★★ 关键的一步 | 两端同色 ⇒ f[l][r] = min(f[l+1][r], f[l][r−1])(那一刷同时服务两端) |
| 第一版:没特判 | ★ 恒等于串长 n(300/300)—— 纯区间划分下任何一刷都不能跨过分界线 |
★★ 特判写成 f[l+1][r−1]+1 |
恒 ≥ 正解;最小反例只要 AAA(1 vs 2);被抓 67/300 |
| ★ 两组官方样例 | 第一组两个错法全挡住,第二组只挡住一个 ⇒ 给了几组就跑几组 |
| ★★ 参照物 | 不是另一份 DP,是在木板状态图上 BFS(和 DP 一个字不共享,300 轮 0 不一致) |
| ★★ 自检的性质 | 压掉连续重复,答案不变(300/300,其中 270 轮真的变短) |
| 规模 | 顶格 n = 50 只有 20 225 次转移 —— 这道题考的全是那一条特判 |