洛谷——线性+区间与环形dp

尼克的任务

题目描述

尼克每天上班之前都连接上英特网,接收他的上司发来的邮件,这些邮件包含了尼克主管的部门当天要完成的全部任务,每个任务由一个开始时刻与一个持续时间构成。

尼克的一个工作日为 n n n 分钟,从第 1 1 1 分钟开始到第 n n n 分钟结束。当尼克到达单位后他就开始干活,公司一共有 k k k 个任务需要完成。如果在同一时刻有多个任务需要完成,尼克可以任选其中的一个来做,而其余的则由他的同事完成,反之如果只有一个任务,则该任务必需由尼克去完成,假如某些任务开始时刻尼克正在工作,则这些任务也由尼克的同事完成。如果某任务于第 p p p 分钟开始,持续时间为 t t t 分钟,则该任务将在第 ( p + t − 1 ) (p+t-1) (p+t1) 分钟结束。

写一个程序计算尼克应该如何选取任务,才能获得最大的空暇时间。

输入格式

输入数据第一行含两个用空格隔开的整数 n n n k k k

接下来共有 k k k 行,每一行有两个用空格隔开的整数 p p p t t t,表示该任务从第 p p p 分钟开始,持续时间为 t t t 分钟。

输出格式

输出文件仅一行,包含一个整数,表示尼克可能获得的最大空暇时间。

样例 #1

样例输入 #1

15 6
1 2
1 6
4 11
8 5
8 1
11 5

样例输出 #1

4

提示

数据规模与约定
  • 对于 100 % 100\% 100% 的数据,保证 1 ≤ n ≤ 1 0 4 , 1 ≤ k ≤ 1 0 4 , 1 ≤ p ≤ n , 1 ≤ p + t − 1 ≤ n 1 \leq n \leq 10^4,1 \leq k \leq 10^4,1 \leq p \leq n,1 \leq p+t-1 \leq n 1n104,1k104,1pn,1p+t1n

思路

  1. 首先,正常的思路是定义状态为f[i],集合:i时能获得的空闲时间集合,属性:max。当状态计算时,f[i]的最优解受到前面时刻选择哪个任务持续时间的影响,而我们并不能保证前面时刻的最优解可以推导出后面时刻的最优解。比如第1时刻有两个任务一个是1~2,一个是1~9,而此后的任务是4~15,如果按照此方法在前四秒的最优方案是选择第一时刻,而选择到后面的任务时,前四秒的最优方案是选择第二个方案。所以不满足最优子结构原则。
  2. 既然任务的选择会影响到后面时刻的选择,为了使得无后效性,可以从反着来进行计算。这样每一时刻的选择,都有一个已算出最优子问题的解。具体的状态流程如下:
  3. 状态表示:f[i]
    集合: 表示i~n时间内的空闲时间的集合
    属性:max
    状态计算:
    f[i] = max(f[i + a[k]]) 如果i时刻存在任务k
    f[i] = f[i + 1] + 1如果i时刻没有任务
  4. 值得一提的是,本题可以抽象为一个分组背包问题,时间为容量,每一时刻的任务可以作为物品组中的物品。

代码

N = 10010
f = [0] * N
a = [[] for _ in range(N)] #存储i时刻的任务情况

n, m = map(int, input().split())

for i in range(m) :
	s, l = map(int, input().split())
	a[s].append(l)

for i in range(n, 0, -1) : #遍历时刻与背包
	length = len(a[i])
	if length == 0 :
		f[i] = f[i + 1] + 1
	else :
		for j in a[i] :
			f[i] = max(f[i], f[i + j])
print(f[1])

大师

题目背景

建筑大师最近在跟着数学大师 ljt12138 学数学,今天他学了等差数列,ljt12138 决定给他留一道练习题。

题目描述

ljt12138 首先建了 n n n 个特斯拉电磁塔,这些电塔排成一排,从左到右依次标号为 1 1 1 n n n,第 i i i 个电塔的高度为 h [ i ] h[i] h[i]

建筑大师需要从中选出一些电塔,然后这些电塔就会缩到地下去。这时候,如果留在地上的电塔的高度,从左向右构成了一个等差数列,那么这个选择方案就会被认为是美观的。

建筑大师需要求出,一共有多少种美观的选择方案,答案模 998244353 998244353 998244353

注意,如果地上只留了一个或者两个电塔,那么这种方案也是美观的。地上没有电塔的方案被认为是不美观的。

同时也要注意,等差数列的公差也可以为负数。

输入格式

第一行一个正整数 n n n

第二行 n n n 个非负整数,第 i i i 个整数是第 i i i 个电塔的高度 h [ i ] h[i] h[i]

输出格式

输出一个整数,表示美观的方案数模 998244353 998244353 998244353 的值。

样例 #1

样例输入 #1

8
13 14 6 20 27 34 34 41

样例输出 #1

50

样例 #2

样例输入 #2

100
90 1004 171 99 1835 108 81 117 141 126 135 144 81 153 193 81 962 162 1493 171 1780 864 297 180 532 1781 189 1059 198 333 1593 824 207 1877 216 270 225 1131 336 1875 362 234 81 288 1550 243 463 1755 252 406 261 270 279 288 1393 261 1263 297 135 333 872 234 881 180 198 81 225 306 180 90 315 81 81 198 252 81 297 1336 1140 1238 81 198 297 661 81 1372 469 1132 81 126 324 333 342 81 351 481 279 1770 1225 549

样例输出 #2

11153

提示

v v v 为最高的电塔高度。

对于前 30 % 30\% 30% 的数据,$n \le 20 $。

对于前 60 % 60\% 60% 的数据, n ≤ 100 n \le 100 n100 v ≤ 2 × 1 0 3 v \le 2 \times 10^3 v2×103

对于另外 20 % 20\% 20% 的数据,所有电塔的高度构成一个等差数列。

对于 100 % 100\% 100% 的数据, n ≤ 1 0 3 n \le 10^3 n103 v ≤ 2 × 1 0 4 v \leq2 \times 10^4 v2×104

思路

题目要求序列中存在等差数列的数量,可以利用类似于最长上升子序列来进行状态表示和状态计算。
状态表示:f[i, j]
集合:表示以i结尾且公差为j的等差数列的集合
属性:num
状态计算: if nums[i] - nums[k] == j : f[i, j] += (f[k, j] + 1),=>f[i, nums[i] - nums[k]] += (f[k, nums[i] - nums[k]] + 1)

代码

N = 1010
MOD = 998244353
shift = 20010
f = [[1] * shift * 2 for _ in range(N)] #单个数可以是任意等差数列,记为一个方案
nums = [0] * N

n = int(input())
nums[1 : n + 1] = list[map(int, input().split())]

ans = n #初始化为单个数等差数列方案数
# 最长上升子序列模板
for i in range(2, n + 1) :
	for j in range(1, i) :
		equ = nums[i] - nums[j]
		f[i][equ + shift] = (f[i][equ + shift] + f[j][equ + shift]) % MOD
		ans = (ans + f[j][equ + shift]) % MOD

print(ans)		

木棍加工

题目描述

一堆木头棍子共有n根,每根棍子的长度和宽度都是已知的。棍子可以被一台机器一个接一个地加工。机器处理一根棍子之前需要准备时间。准备时间是这样定义的:

第一根棍子的准备时间为1分钟;

如果刚处理完长度为L,宽度为W的棍子,那么如果下一个棍子长度为Li,宽度为Wi,并且满足L>=Li,W>=Wi,这个棍子就不需要准备时间,否则需要1分钟的准备时间;

计算处理完n根棍子所需要的最短准备时间。比如,你有5根棍子,长度和宽度分别为(4, 9),(5, 2),(2, 1),(3, 5),(1, 4),最短准备时间为2(按(4, 9)、(3, 5)、(1, 4)、(5, 2)、(2, 1)的次序进行加工)。

输入格式

第一行是一个整数n(n<=5000),第2行是2n个整数,分别是L1,W1,L2,w2,…,Ln,Wn。L和W的值均不超过10000,相邻两数之间用空格分开。

输出格式

仅一行,一个整数,所需要的最短准备时间。

样例 #1

样例输入 #1

5
4 9 5 2 2 1 3 5 1 4

样例输出 #1

2

思路

狄尔沃斯定理

  1. 定义
    对于任意有限偏序集,其最大反链中元素的数目必等于最小链划分中链的数目。此定理的对偶形式亦真,它断言:对于任意有限偏序集,其最长链中元素的数目必等于其最小反链划分中反链的数目
  2. 最长上升子序列中的应用
    最大上升子序列长度 ≡ 不上升子序列的最小划分 最大下降子序列长度 ≡ 不下降子序列的最小划分 最大上升子序列长度\equiv不上升子序列的最小划分\\最大下降子序列长度\equiv不下降子序列的最小划分 最大上升子序列长度不上升子序列的最小划分最大下降子序列长度不下降子序列的最小划分

本题想法

如果下一个棍子长度为Li,宽度为Wi,并且满足L>=Li,W>=Wi,这个棍子就不需要准备时间,否则需要1分钟的准备时间
即求出长度和宽度共同的不上升子序列划分最少,按照dilworth定理可知,即求最大上升子序列。对于本题的二维数据,需要使用一点贪心的思维,因为两维都要求不上升,那么先让第一维按照降序排序,其次再让第二维按照降序排序,最后使用最长上升子序列模型

代码

N = 5010

f = [1] * N
nums = []
n = int(input())

tmp = list(map(int, input().split()))
for i in range(n) :
	nums.append([tmp[i * 2], tmp[i * 2 + 1]])
nums.sort(reverse = True)

res = 1
for i in range(1, n + 1) :
	for j in range(1, i) :
		if nums[i - 1][1] > nums[j - 1][1] :
			f[i] = max(f[i], f[j] + 1)
			res = max(res, f[i])
print(res)

线性dp总结

线性dp一般大致分为序列问题和背包问题,要熟练掌握数字三角形、最长上升子序列模型等

关路灯

题目描述

某一村庄在一条路线上安装了 n n n 盏路灯,每盏灯的功率有大有小(即同一段时间内消耗的电量有多有少)。老张就住在这条路中间某一路灯旁,他有一项工作就是每天早上天亮时一盏一盏地关掉这些路灯。

为了给村里节省电费,老张记录下了每盏路灯的位置和功率,他每次关灯时也都是尽快地去关,但是老张不知道怎样去关灯才能够最节省电。他每天都是在天亮时首先关掉自己所处位置的路灯,然后可以向左也可以向右去关灯。开始他以为先算一下左边路灯的总功率再算一下右边路灯的总功率,然后选择先关掉功率大的一边,再回过头来关掉另一边的路灯,而事实并非如此,因为在关的过程中适当地调头有可能会更省一些。

现在已知老张走的速度为 1 m / s 1m/s 1m/s,每个路灯的位置(是一个整数,即距路线起点的距离,单位: m m m)、功率( W W W),老张关灯所用的时间很短而可以忽略不计。

请你为老张编一程序来安排关灯的顺序,使从老张开始关灯时刻算起所有灯消耗电最少(灯关掉后便不再消耗电了)。

输入格式

第一行是两个数字 n n n(表示路灯的总数)和 c c c(老张所处位置的路灯号);

接下来 n n n 行,每行两个数据,表示第 1 1 1 盏到第 n n n 盏路灯的位置和功率。数据保证路灯位置单调递增。

输出格式

一个数据,即最少的功耗(单位: J J J 1 J = 1 W × s 1J=1W\times s 1J=1W×s)。

样例 #1

样例输入 #1

5 3
2 10
3 20
5 20
6 30
8 10

样例输出 #1

270

提示

样例解释

此时关灯顺序为 3 4 2 1 5

数据范围

1 ≤ n ≤ 50 1\le n\le50 1n50 1 ≤ c ≤ n 1\le c\le n 1cn

思路

一道很好的区间dp+状态机的问题
老张关掉一个灯后,有两种选择,1、继续沿着上次的方向关灯,2、向着相反方向关灯

老张走过的路上的灯一定被关掉,所以老张关灯的过程就是一个区间扩大的过程。
从集合的角度思考:
状态表示f[l, r, 0], f[l, r, 1]:
集合:l~r区间的灯被关,且最终老张位于左边0、右边1所用时间的集合
属性:min
状态计算:
f[l, r, 0] = min(f[l + 1, r, 0] + path[l + 1 ~ l], f[l + 1, r, 1] + path[r ~ l])
f[l, r, 1] = min(f[l, r - 1, 1] + path[r - 1~r], f[l, r - 1, 0] + path[l ~ r])

代码

N = 55
INF = 100010
f = [[[INF] * 2 for _ in range(N)] for _ in range(N)]
path = [0] * N
energe = [0] * N

n, m = map(int, input().split())
for i in range(1, n + 1) :
	p, e = map(int, input().split())
	energe[i] += (energe[i - 1] + e)
	path[i] = p

f[m][m] = [0, 0]
for length in range(2, n + 1) :
	for l in range(1, n + 1) :
		r = l + length - 1
		if r > n : break
		left, right = energe[l] + energe[n] - energe[r], energe[l - 1] + energe[n] - energe[r - 1]
		f[l][r][0] = min(f[l + 1][r][0] + left * (path[l + 1] - path[l]), f[l + 1][r][1] + left * (path[r] - path[l]))
		f[l][r][1] = min(f[l][r - 1][1] + right * (path[r] - path[r - 1]), f[l][r - 1][0] + right * (path[r] - path[l]))

print(min(f[1][n][0], f[1][n][1]))

[NOIP2007 提高组] 矩阵取数游戏

题目描述

帅帅经常跟同学玩一个矩阵取数游戏:对于一个给定的 n × m n \times m n×m 的矩阵,矩阵中的每个元素 a i , j a_{i,j} ai,j 均为非负整数。游戏规则如下:

  1. 每次取数时须从每行各取走一个元素,共 n n n 个。经过 m m m 次后取完矩阵内所有元素;
  2. 每次取走的各个元素只能是该元素所在行的行首或行尾;
  3. 每次取数都有一个得分值,为每行取数的得分之和,每行取数的得分 = 被取走的元素值 × 2 i \times 2^i ×2i,其中 i i i 表示第 i i i 次取数(从 1 1 1 开始编号);
  4. 游戏结束总得分为 m m m 次取数得分之和。

帅帅想请你帮忙写一个程序,对于任意矩阵,可以求出取数后的最大得分。

输入格式

输入文件包括 n + 1 n+1 n+1 行:

第一行为两个用空格隔开的整数 n n n m m m

2 ∼ n + 1 2\sim n+1 2n+1 行为 n × m n \times m n×m 矩阵,其中每行有 m m m 个用单个空格隔开的非负整数。

输出格式

输出文件仅包含 1 1 1 行,为一个整数,即输入矩阵取数后的最大得分。

样例 #1

样例输入 #1

2 3
1 2 3
3 4 2

样例输出 #1

82

提示

【数据范围】

对于 60 % 60\% 60% 的数据,满足 1 ≤ n , m ≤ 30 1\le n,m\le 30 1n,m30,答案不超过 1 0 16 10^{16} 1016
对于 100 % 100\% 100% 的数据,满足 1 ≤ n , m ≤ 80 1\le n,m\le 80 1n,m80 0 ≤ a i , j ≤ 1000 0\le a_{i,j}\le1000 0ai,j1000

【题目来源】

NOIP 2007 提高第三题。

思路

  1. 首先,对于每行取数得分之间相互独立,所以在求取数后的最大得分,可以通过求每行取数最大得分之和得到结果。
  2. 具体的,在求每行取数最大得分时,由于每次取走元素都为行首或者行尾元素。第i次取元素,就是从长度为n - i + 1的序列中取首或者尾的元素。这里需要维护一个待取元素的区间(l, r), l - r = n - i
  3. 状态表示f[l, r]:
    集合:表示所剩元素为l~r序列时,从其中取数所得分数的集合
    属性:max
    状态计算:f[l, r] = max(f[l + 1, r] + nums[l] * 2 ** times[l, r], f[l, r - 1] + nums[r] * 2 ** times[l, r])

代码

n, m = map(int, input().split())
res = 0
for i in range(n) :
	nums = list(map(int, input().split()))
	f = [[0] * (m + 1) for i in range(m + 1)]
	for length in range(1, m + 1) :
		for l in range(1, m + 1) :
			r = l + length - 1
			if r > m : break
			if length == 1 :
				f[l][r] = nums[l - 1] * 2 ** m
			else :
				left, right = nums[l - 1] * 2 ** (m - length + 1), nums[r - 1] * 2 ** (m - length + 1)
				f[l][r] = max(f[l + 1][r] + left, f[l][r - 1] + right)
	res += f[1][m]
print(res)		

[USACO16OPEN]248 G

题目描述

Bessie likes downloading games to play on her cell phone, even though she doesfind the small touch screen rather cumbersome to use with her large hooves.

She is particularly intrigued by the current game she is playing.The game starts with a sequence of N N N positive integers ( 2 ≤ N ≤ 248 2 \leq N\leq 248 2N248), each in the range 1 … 40 1 \ldots 40 140. In one move, Bessie cantake two adjacent numbers with equal values and replace them a singlenumber of value one greater (e.g., she might replace two adjacent 7swith an 8). The goal is to maximize the value of the largest numberpresent in the sequence at the end of the game. Please help Bessiescore as highly as possible!

给定一个1*n的地图,在里面玩2048,每次可以合并相邻两个(数值范围1-40),问序列中出现的最大数字的值最大是多少。注意合并后的数值并非加倍而是+1,例如2与2合并后的数值为3。

输入格式

The first line of input contains N N N, and the next N N N lines give the sequence

of N N N numbers at the start of the game.

输出格式

Please output the largest integer Bessie can generate.

样例 #1

样例输入 #1

4
1
1
1
2

样例输出 #1

3

提示

In this example shown here, Bessie first merges the second and third 1s to

obtain the sequence 1 2 2, and then she merges the 2s into a 3. Note that it is

not optimal to join the first two 1s.

思路

  1. 首先,对于给定的一个序列合并的话,要么可以完全合并,要么存在不能合并的项。
  2. 我们从大区间可由哪些区间组成的角度考虑,类似于石子合并问题。
  3. 状态表示f[l, r]:
    集合:表示l,r区域完全合并数值的集合
    属性:max
    状态计算:
    f[l, r] = a[l] if l == r
    f[l, r] = f[l, k] + 1 if l != r, f[l, k] == f[k + 1, r]

代码

N = 250
a = [0] * N
f = [[0] * N for _ in range(N)]
ans = 0

n = int(input())
for i in range(1, n + 1) :
	a[i] = int(input())

for length in range(1, n + 1) :
	for l in range(1, n + 1) :
		r = l + length - 1
		if r > n : break
		if length == 1 :
			f[l][r] = a[l]
		else :
			for k in range(l, r) :
				if f[l][k] == f[k + 1][r] :
					f[l][r] = max(f[l][r], f[l][k] + 1)
		ans = max(ans, f[l][r])
print(ans)

[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 1n10

100 % 100\% 100% 的数据满足 1 ≤ n ≤ 50 1\le n\le 50 1n50

思路

  1. 从小区间往大区间思考,如果小区间l + 1~r扩展到l ~ r,如果l位置颜色与r相同,则r涂色操作,可以覆盖l位置涂色操作。同理l涂色操作也可以覆盖r同色的涂色操作,如果l,r颜色不同则无法覆盖,我们需要考虑将子串断成两部分来涂色
  2. 状态表示f[l, r]:
    集合:表示l~r涂色操作数量集合
    属性:min
    状态计算:
    f[l, r] = 1 if length == 1
    f[l, r] = max(f[l + 1, r], f[l, r - 1]) if l != r and a[l] == a[r]
    f[l, r] = max(f[l, k] + f[k + 1, r]) if l != r and a[l] != a[r]

代码

本题代码正确,但在洛谷Python无法通过,可以转为cpp

N = 55
f = [[N] * N for _ in range(N)]

a = input()
n = len(a)
for length in range(1, n + 1) :
	for l in range(1, n + 1) :
		r = l + length - 1
		if r > n : break
		if length == 1 :
			f[l][r] = 1
		else :
			if a[l - 1] == a[r - 1] :
				f[l][r] = min(f[l + 1][r], f[l][r - 1])
			else :
				for k in range(l, r) :
					f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r])
print(f[1][n])

cpp代码

#include<iostream>
#include<cstring>
#include<string>

using namespace std;

const int N = 55;

int f[N][N];
string a;
int n;

int main() {
	cin >> a;
	n = a.length();
	memset(f, 0x7f, sizeof(f));
	for(int length = 1; length <= n; length ++) {
		for (int l = 1; l + length - 1 <= n; l ++) {
			int r = l + length - 1;
			if (length == 1) f[l][r] = 1;
			else if (a[l - 1] == a[r - 1]) f[l][r] = min(f[l + 1][r], f[l][r - 1]);
			else {
				for(int k = l; k < r; k ++) f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r]);
			}
		}
	}
	cout << f[1][n] << endl;
	return 0;
}

Zuma

题面翻译

题目描述

Genos最近在他的手机上下载了祖玛游戏。在祖玛游戏里,存在n个一行的宝石,第i个宝石的颜色是Ci 。这个游戏的目标是尽快的消灭一行中所有的宝石。 在一秒钟,Genos能很快的挑选出这些有颜色的宝石中的一个回文的,连续的子串,并将这个子串移除。每当一个子串被删除后,剩余的宝石将连接在一起,形成一个新的行列。你的任务是:求出把整个宝石串都移除的最短时间。 让我们给你一个提示:如果一个串正着读或倒着读都一样,那么这个串(或子串)叫回文串。在我们这道题中,“回文”指这个宝石串中的第一个珠子的颜色等于最后一个珠子的颜色,第二个珠子的颜色等于倒数第二个珠子的颜色,等等。
输入输出格式
输入格式:

第一行包含一个整数n(1<=n<=500) ——宝石串的长度。 第二行包含n个被空格分开的整数,第i(1<=i<=n) 个表示这行中第i个珠子的颜色。
输出格式:

输出一个整数,把这行珠子移除的最短时间。 (样例略)
说明:

在第一个例子中,Genos可以在一秒钟就把这行珠子全部移走。 在第二个例子中,Genos一次只能移走一个珠子,所以移走三个珠子花费他三秒。 在第三个例子中,为了达到2秒的最快时间,先移除回文串4 4,再移除回文串1 2 3 2 1。

感谢@Administrator2004 提供的翻译

题目描述

Genos recently installed the game Zuma on his phone. In Zuma there exists a line of $ n $ gemstones, the $ i $ -th of which has color $ c_{i} $ . The goal of the game is to destroy all the gemstones in the line as quickly as possible.

In one second, Genos is able to choose exactly one continuous substring of colored gemstones that is a palindrome and remove it from the line. After the substring is removed, the remaining gemstones shift to form a solid line again. What is the minimum number of seconds needed to destroy the entire line?

Let us remind, that the string (or substring) is called palindrome, if it reads same backwards or forward. In our case this means the color of the first gemstone is equal to the color of the last one, the color of the second gemstone is equal to the color of the next to last and so on.

输入格式

The first line of input contains a single integer $ n $ ( $ 1<=n<=500 $ ) — the number of gemstones.

The second line contains $ n $ space-separated integers, the $ i $ -th of which is $ c_{i} $ ( $ 1<=c_{i}<=n $ ) — the color of the $ i $ -th gemstone in a line.

输出格式

Print a single integer — the minimum number of seconds needed to destroy the entire line.

样例 #1

样例输入 #1

3
1 2 1

样例输出 #1

1

样例 #2

样例输入 #2

3
1 2 3

样例输出 #2

3

样例 #3

样例输入 #3

7
1 4 4 2 3 2 1

样例输出 #3

2

提示

In the first sample, Genos can destroy the entire line in one second.

In the second sample, Genos can only destroy one gemstone at a time, so destroying three gemstones takes three seconds.

In the third sample, to achieve the optimal time of two seconds, destroy palindrome 4 4 first and then destroy palindrome 1 2 3 2 1.

思路

  1. 题意理解:给定一个字符串,每次可以移除其中的回文串,问最少移除几次可以移完。
  2. 对于整个字符串来说,每次的移除都可以看做是移除其中的小区间。
  3. 从大区间往小区间的角度来看,对于l~r中的字符串中,如果a[l] == a[r],那么此时操作等于l + 1~r - 1字符串中的移除操作。如果不相等,则枚举分界点,进行合并操作。
  4. 状态表示:f[l, r]
    集合:l~r区间全部移除操作时间集合
    属性:min
    状态计算:
    f[l, r] = f[l + 1, r - 1](a[l] == a[r])
    f[l, r] = min(f[l, k] + f[k + 1, r])

代码

N = 510
f = [[N] * N for _ in range(N)]
a = [0] * N

n = int(input())
a[1 : n + 1] = list(map(int, input().split()))

for length in range(1, n + 1) :
	for l in range(1, n + 1) :
		r = l + length - 1
		if r > n : break
		if length == 1 : 
			f[l][r] = 1
		elif length == 2 :
			if a[l] == a[r] :
				f[l][r] = 1
			else : 
				f[l][r] = 2
		elif a[l] == a[r] :
			f[l][r] = f[l + 1][r - 1]
		else :
			for k in range(l, r) :
				f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r])
print(f[1][n])
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值