3996 涂色(区间dp)

1. 问题描述:

有 n 个砖块排成一排,从左到右编号为 1∼n。其中,第 i 个砖块的初始颜色为 ci。我们规定,如果编号范围 [i,j] 内的所有砖块的颜色都相同,则砖块 i 和 j 属于同一个连通块。例如,[3,3,3] 有 1 个连通块,[5,2,4,4] 有 3 个连通块。现在,要对砖块进行涂色操作。开始所有操作之前,你需要任选一个砖块作为起始砖块。
每次操作:
任选一种颜色。
将最开始选定的起始砖块所在连通块中包含的所有砖块都涂为选定颜色,
请问,至少需要多少次操作,才能使所有砖块都具有同一种颜色。

输入格式

第一行包含整数 n。第二行包含 n 个整数 c1,c2,…,cn。

输出格式

一个整数,表示所需要的最少操作次数。

数据范围

前六个测试点满足,1 ≤ n ≤ 20。
所有测试点满足,1 ≤ n ≤ 5000,1 ≤ ci  ≤ 5000。

输入样例1:

4
5 2 2 1

输出样例1:

2

输入样例2:

8
4 5 2 2 1 3 5 5

输出样例2:

4

输入样例3:

1
4

输出样例3:

0

输入样例4:

5
1 3 1 2 1

输出样例4:

3
样例4解释
注意,每次染色操作所涉及的连通块必须包含所有操作开始前选定的起始砖块。因此,答案是 3,而不是 2。
来源:https://www.acwing.com/problem/content/3999/

2. 思路分析:

分析题目可以知道数据范围最大为5000,所以需要将时间复杂度控制在O(n ^ 2)或者O(n ^ 2logn)以内,而且在开始的时候需要选择起始块向两边进行扩展,由于比较难想到直接的方法进行处理,结合数据范围我们考虑如何贪心或者dp看能否解决,一般的dp时间复杂度可以控制在O(n ^ 2)以内,所以我们看能否使用dp来解决,主要从两个方面考虑:状态表示和状态计算,我们可以结合题目的描述确定可以声明多少维的数组,可以发现我们声明两维的数组即可,其中f[i][j]表示所有起点从区间[i,j]任选且最终操作成同色的所有方案的最小步数(结合题目的描述确定状态表示),为了方便后面处理对输入的区间按块进行划分,把连续相等的一段区间看成是一个点;如何进行状态计算呢?这里需要分情况讨论,对于当前区间的[i,j],当ci != cj的时候最终颜色要么等于ci要么等于cj,而且c[i + 1,j]的颜色都是不一样的所以要想使得f[i,j]最小那么需要使得区间[i + 1,j]最小,根据f的定义可以知道恰好是f(i + 1,j),所以当ci != cj的时候为:f(i,j) = min(f(i + 1,j),f(i,j - 1)) + 1;对于第二种情况ci = cj,此时的起点能否选到i,j呢?不妨设起点为i,此时只能一路向右边扩展,分为三步进行扩展:

  • 扩到i + 1, + 1
  • 扩到j - 1,>= f(i + 1,j - 1) - 1 有可能变色之后多了一步
  • 扩到j,+ 1

所以总共的最小步数为: >= f(i  + 1,j - 1) + 1,而当起点为区间[i + 1,j - 1]中间的某个点的时候那么可以先把中间部分先扩好,然后最后将区间[i + 1,j - 1]染色ci或者cj的颜色即可,为f(i + 1, j - 1) + 1,综上当ci == cj的时候f(i,j) = (i + 1,j - 1) + 1,我们在写代码的时候可以按照区间dp的思路从长度较小的区间递推得到长度较大的区间的dp值。

3. 代码如下:

class Solution:
    def process(self):
        n = int(input())
        t = list(map(int, input().split()))
        # c存储的是连续不相等的一段
        c = list()
        for x in t:
            if c and c[-1] == x:
                # 当前数字与上一个数字相同那么需要跳过并且长度n需要减1
                n -= 1
                continue
            c.append(x)
        f = [[0] * (n + 10) for i in range(n + 10)]
        # 枚举所有的长度
        for l in range(2, n + 1):
            i = 0
            while i + l - 1 < n:
                j = i + l - 1
                # 分情况讨论
                if c[i] != c[j]:
                    f[i][j] = min(f[i + 1][j], f[i][j - 1]) + 1
                else:
                    f[i][j] = f[i + 1][j - 1] + 1
                i += 1
        return f[0][n - 1]


if __name__ == '__main__':
    print(Solution().process())
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值