信奥赛CSP-J复赛集训(DP专题)(37):P4170 [CQOI2007] 涂色
信奥赛CSP-J复赛集训(DP专题)(37):P4170 [CQOI2007] 涂色
题目描述
假设你有一条长度为 5 5 5 的木板,初始时没有涂过任何颜色。你希望把它的 5 5 5 个单位长度分别涂上红、绿、蓝、绿、红色,用一个长度为 5 5 5 的字符串表示这个目标: RGBGR \texttt{RGBGR} RGBGR。
每次你可以把一段连续的木板涂成一个给定的颜色,后涂的颜色覆盖先涂的颜色。例如第一次把木板涂成 RRRRR \texttt{RRRRR} RRRRR,第二次涂成 RGGGR \texttt{RGGGR} RGGGR,第三次涂成 RGBGR \texttt{RGBGR} RGBGR,达到目标。
用尽量少的涂色次数达到目标。
输入格式
输入仅一行,包含一个长度为 n n n 的字符串,即涂色目标。字符串中的每个字符都是一个大写字母,不同的字母代表不同颜色,相同的字母代表相同颜色。
输出格式
仅一行,包含一个数,即最少的涂色次数。
输入输出样例 #1
输入 #1
AAAAA
输出 #1
1
输入输出样例 #2
输入 #2
RGBGR
输出 #2
3
说明/提示
40 % 40\% 40% 的数据满足 1 ≤ n ≤ 10 1\le n\le 10 1≤n≤10。
100 % 100\% 100% 的数据满足 1 ≤ n ≤ 50 1\le n\le 50 1≤n≤50。
AC代码:
#include<bits/stdc++.h>
using namespace std;char s[60]; // 存储输入的字符串
int dp[60][60]; // dp[l][r]表示将区间[l..r]涂成目标颜色所需的最少次数int main(){scanf("%s", s + 1); // 从s[1]开始读取字符串int n = strlen(s + 1); // 获取字符串长度memset(dp, 0x3f, sizeof(dp)); // 初始化为极大值,表示不可达// 初始化单个字符的情况for(int i = 1; i <= n; i++){dp[i][i] = 1; // 单个字符只需涂一次}// 动态规划处理所有区间for(int k = 2; k <= n; k++){ // 枚举区间长度,从2到nfor(int l = 1; l + k - 1 <= n; l++){ // 枚举区间左端点lint r = l + k - 1; // 计算区间右端点r// 如果区间两端字符相同,则可能合并涂色if(s[l] == s[r]){dp[l][r] = min(dp[l + 1][r], dp[l][r - 1]); // 取不包含左端点或不包含右端点的子区间的最小值}// 遍历所有可能的分割点,寻找更优解for(int i = l; i < r; i++){dp[l][r] = min(dp[l][r], dp[l][i] + dp[i + 1][r]);}}}printf("%d\n", dp[1][n]); // 输出整个字符串的最少涂色次数return 0;
}
功能分析:
该程序采用动态规划解决区间涂色问题,关键点如下:
-
状态定义:
dp[l][r]
表示将区间[l..r]
涂成目标颜色所需的最少次数。 -
初始化:每个单字符区间
dp[i][i]
初始化为1,因为只需一次涂色。 -
状态转移:
- 两端相同处理:若区间两端字符相同,则
dp[l][r]
可取左右子区间的最小值,因为可以在涂色时覆盖端点。 - 分割点遍历:枚举所有可能的分割点
i
,将区间分为[l..i]
和[i+1..r]
,取两部分次数和的最小值。
- 两端相同处理:若区间两端字符相同,则
-
自底向上计算:从小区间逐步求解大区间,确保每个区间的解都基于最优子结构。
-
复杂度:时间复杂度为O(n³),适用于题目中的n≤50的数据范围。
文末彩蛋:
关注并查看老师的个人主页,学习完整csp信奥赛完整系列课程: https://edu.csdn.net/lecturer/7901