刷题记录:牛客NC19909[CQOI2007]涂色PAINT

传送门:牛客

题目描述:

假设你有一条长度为5的木版,初始时没有涂过任何颜色。你希望把它的5个单位长度分别涂上红、绿、蓝、
绿、红色,用一个长度为5的字符串表示这个目标:RGBGR。 每次你可以把一段连续的木版涂成一个给定的
颜色,后涂的颜色覆盖先涂的颜色。
例如第一次把木版涂成RRRRR,第二次涂成RGGGR,第三次涂成RGBGR,达到目标。 用尽量少的涂色次
数达到目标。
输入:
RGBGR
输出:
3

这道题的思路不难想,但是我在做这道题的过程中遇到了不少思维的坑,所以准备详细记录一下思考过程

首先对于这道题,我当时想到将原字符串进行一个压缩,因为涂色是一个范围形式的,所以我们可以直接将相邻的字符串进行压缩,获得一个新的相邻不重复的字符串

然后我随便想了想,发现这道题需要对左右端点的字符串是否相同进行分类(想到了回文子串),所以我当时想的是假设我们的左右端点的字符是一样的话显然我们可以直接去掉两端点了.但是显然这个思路是错误的,因为有可能将整个区间进行染色的过程中对中间区间有影响.有一个hack数据:ABACA

然后我仔细想了一下如何改变一下我的刚开始的思路,然后我就发现了,我们发现此时的区间 [ l , r ] [l,r] [l,r]两个端点的字符是一样的,所以假设我们将 [ l , r ] [l,r] [l,r]进行一个整体染色的话,其实我们的代价和对 [ l , r − 1 ] [l,r-1] [l,r1]部分进行染色是一样的,因为我们刚开始是没哟任何颜色的,所以当我们涂下左端点的那一刻其实我们就可以顺带涂掉我们的右端点(此处如果有人有疑问为什么我们涂整个区间是最优的.因为当我们涂整个区间时,我们可以将除了两端点的剩下的区间当成一个没有染过色的区间,对于中间没有影响,但是如果我们没有同时涂的话,我们就需要花至少两次),所以此时就有dp方程:

d p [ l ] [ r ] = d p [ l + 1 ] [ r ] 或 者 d p [ l ] [ r ] = d p [ l ] [ r − 1 ] dp[l][r]=dp[l+1][r]或者dp[l][r]=dp[l][r-1] dp[l][r]=dp[l+1][r]dp[l][r]=dp[l][r1]

接下来就是对两端点不同的区间进行转移了:

在提出转移方程之前,我们需要确定几个事实:

  1. 对于我们最优的染色方案,我们可以只是用不交叉区间进行染色 (但是可以有包含的区间)
    为什么呢,因为假设我们有两个区间进行叠加了,那么显然的因为覆盖较后染色的会覆盖之前的,也就是相当于之前的没有染色,所以我们可以将之前交叉的区间改成较后的染色区间即可
  2. 在一事实的基础上,我们可以发现我们一定能找到一个分界点使得我们的染色区间不跨越这个点.
    为什么呢因为我们发现我们可以将被包含的区间进行忽略,只要考虑大区间即可,对于大区间我们显然有不交叉且互补的性质,那么我们显然可以找到一个点将所有的大区间分成两部分(假设不止两个区间我们也可以将其合并为两个区间)
  3. 对于我们的分开的两个大区间,显然我们可以进行各自的染色,因为没有交叉意味着没有干扰!!,也就是说我们最后的答案就是分开的贡献和!!,至此我们就有区间dp的正确性保证了

在基于以上事实之后我们自然而然的就有区间dp的想法了

d p [ l ] [ r ] = m i n ( d p [ l ] [ r ] , d p [ l ] [ k ] + d p [ k + 1 ] [ r ] ) ; dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]); dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]);

下面是具体的代码部分:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#include <queue>
#include <string.h>
#include <stack>
#include <deque>
using namespace std;
typedef long long ll;
#define inf 0x3f3f3f3f
#define root 1,n,1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
	ll x=0,w=1;char ch=getchar();
	for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
	for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x*w;
}
#define maxn 1000000
#define ll_maxn 0x3f3f3f3f3f3f3f3f
const double eps=1e-8;
int dp[200][200];
char a[maxn],s[maxn];
int main() {
	cin>>a;s[0]=a[0];int lens=1;
	for(int i=1;i<strlen(a);i++) {//简单的压缩,其实也可以不用压缩
		if(a[i]==a[i-1]) continue;
		s[lens++]=a[i];
	}
	memset(dp,0x3f,sizeof(dp));
	for(int i=0;i<lens;i++) dp[i][i]=1;
	for(int len=2;len<=lens;len++) {
		for(int l=0;l+len-1<lens;l++) {
			int r=l+len-1;
			if(s[l]==s[r]){
				dp[l][r]=dp[l+1][r];
				continue;
			} 
			for(int k=l;k<r;k++) {
				dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]);
			}
		}
	}
	cout<<dp[0][lens-1]<<endl;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值