LA4394 String Painter

题意

给定两个长度相等,只有小写字母构成的字符串s和t,每步可以把s的一个连续子串“刷”成同一个字母,问至少多少步才能把s变成t。

思路

两层区间DP。

首先忽略原始串s,只去看目标串t,设dp1[i][j]表示从什么都没有的情况下利用刷子规则构造出目标串t的i到j这一段所需要的最小操作次数。这里着眼于“最后一次操作”进行阶段的划分。

dp1的状态转移:

  • dp[i][j] = dp[i+1][j ] + 1
    先默认最后一次操作是只刷出最新那个字符。
  • 令k是i和j中间的某个位置,t是目标串,如果t[i] == t[k]
    尝试用dp[i+1][k] + dp[k+1][j]更新dp[i][j]
    因为i处字符等于k处字符,在构造i+1到k这个区段的时候,可以在刷k处字符的同时“顺便”把i位置也给刷了。
    注意这里不可以写成dp[i+1][k-1] + 1 + dp[k+1][j],考虑反例” /e(i对应)/ee/e(k对应)/… “,在这里dp[i+1][k-1]是1,用这种错误的算法dp[i][k]部分会变成2,但实际上应该是1。

接下来引入原始串s,令dp2[i]表示把原始串1到i构造成目标串1到i的部分所需要的最小的操作次数。这里有状态转移:

  • 默认dp2 = dp1[1][i]
  • 如果s[i] == t[i]
    尝试dp2[i] = dp2[i-1]
    否则尝试dp2[i] = dp2[i-1] + 1

    这里的意思是如果在第i位两个串对应字符相等,可以选择无需对这一位进行操作

  • 令k是1到i之间的位置
    尝试dp2[i] = dp2[k-1] + dp1[k][j]

    这里的意思是假设最后k位使用不考虑原始状态的构造方法,前面继承最优解。为什么要这样做?考虑fabcf这样的区间,一种较好的策略是先都刷成f,然后分别刷好abc,如果你选择这么做了,相当于已经无视了原来串的样子,因为第一步已经将全部中间字符都刷成f了。

代码

 

POJ 2955 Brackets

题意

给出一个由小括号喝中括号组成的括号序列,定义正规括号序列为(),[],(s),[s],其中s也是正规括号序列。求最长正规括号序列的长度。

思路

考虑区间DP,设计状态时着眼于“最后一次加括号”。

状态表示:dp[i][j] = 从 i 到 j 的(包含i,j)最长的括号序列长度。

状态转移:

  • dp[i][j] = dp[i][j-1] 继承上一个区间的结果
  • 令k为i,j之间的一个位置,str是给出的括号串(令下标从1开始)
  • 如果str[k]与str[j]正好是一对匹配的括号,可以考虑在最后一步放这个括号,用dp[i][k] + 2 + dp[k+1][j-1]尝试更新dp[i][j]

答案:dp[1][len]

初始化:直接令整个dp数组都为0,既可以完成初始化,也可以无效化所有无效状态。

代码

 

HDU5396 / 15多校9-1 Expression

题意

给出了一个算术表达式,表达式里面只会含有加减乘三种运算。我们每一次从中选取两个数字并执行这两个数字对应的运算,运算结果放回式子中继续进行运算,直到最后只剩下了一个数。让我们求的是每一种不同的选取方式所得到的结果的和。

注意

这个题没有做好的主要原因是没有领会到题目中关于不同的选取方式的定义,注意到题目中讲到只要有一轮选取的数字不一样,就视为不同。这句话的含义中有两个信息:一是允许重复,二是要分先后

思路

最开始确实是没什么思路的,后来看了看board发现并不是特别难的题。于是尝试拆解问题,我们可以从算数式的一小段入手,以sample的1 + 4 * 6 – 8 * 3为例。

只考虑1 + 4部分,只有一种配置方法,就是(1 + 4);
考虑1 + 4 * 6部分,我们发现这一部分可以看成两种情况:1 + (4 * 6)和(1 + 4) * 6,在这里面我们已经考虑了1 + 4区段,而4 * 6区段只有一种配置。这样1 + 4 * 6这个区段就有两种配置了。
接下来考虑1 + 4 * 6 – 8区段,要想得到这个区段的全部情况,我们应该考虑:
1 + (4 * 6 – 8),1 + 4 * (6 – 8),(1 + 4) * 6 – 8,(1 + 4 * 6) – 8这些情况,其中(4 * 6 – 8)又可以看成考虑(4 * 6) – 8,4 * (6 – 8)这两种情况,而(1 + 4 * 6)的对应情况我们先前已经算好了。
最后,考虑1 + 4 * 6 – 8 * 3,顺着上面的思路来,就可以拆分成:
1 + (4 * 6 – 8 * 3),1 + 4 * (6 – 8 * 3),(1 + 4) * 6 – (8 * 3),(1 + 4 * 6) – 8 * 3,(1 + 4 * 6 – 8) * 3,接下来再去拆分这五种情况中的子情况。。。

如果比较敏感的话应该已经发现这就是典型的区间dp了,在推方程的时候,我们是将所有子情况的答案累加,得到当前情况的答案,但这还不够,还要注意每种情况,因为选择顺序的关系,会有多重副本,而这个副本的数量其实蛮好求,就是阶乘,而还要注意,我们在讨论当前情况的时候,是将整个等待处理的算式区段分成了(左区段)算子(分界点)算子(右区段)这样看待的,那么先后的问题又来了,我们不光左右区段的答案自身要乘上阶乘,最后整个结果还要乘上一个组合数,因为左右区段的操作又是分先后的。

以下引自官方题解,注意第2段第2行的dp[l][r]实际上是dp[l][k]

 

QQ20150820-1@2x

代码

测试数据

 

Scroll to top