0x53 区间DP

0x53 区间DP

到目前为止,我们介绍的线性DP一般从初态开始,沿着阶段的扩张向某个方向递推,直至计算出目标状态。区间DP也属于线性DP中的一种,它以“区间长度”作为DP的“阶段”,使用两个坐标(区间的左右端点)描述每个维度。在区间DP中,一个状态由若干个比它更小且包含于它的区间所代表的状态转移而来,因此区间DP的决策往往就是划分区间的方法。区间DP的初态一般就由长度为1的“元区间”构成。这种向下划分、再向上递推的模式与某些树形结构,例如0x43节的线段树,有很大的相似之处。我们把区间DP作为线性DP中一类重要的分支单独进行讲解,使下一节树形DP的内容更容易理解。同时,借助区间DP这种与树形相关的结构,我们也将提及记忆化搜索——其本质是动态规划的递归实现方法。

“多边形”是一款单人益智游戏。在游戏开始时,系统给定玩家一个 N N N边形,该 N N N边形由 N N N个顶点和 N N N条边构成,每条边连接两个相邻的顶点。在每个顶点上写有一个整数,可正可负。在每条边上标有一个运算符“+”(加号)或“*”(乘号)。

第一步,玩家选择一条边,将它删除。接下来在进行 N − 1 N-1 N1步,在每一步中,玩家选择一条边,把这条边以及该边连接的两个顶点用一个新的顶点代替,新顶点上的整数值等于删去的两个顶点上的数按照删去的边上标有的符号进行计算得到的结果。如下图所示,就是一盘游戏的过程。

在这里插入图片描述

最终,游戏仅剩一个顶点,顶点上的数值就是玩家的得分,上图玩家得分为 0。

请计算对于给定的 N N N边形,玩家最高能获得多少分,以及第一步有哪些策略可以使玩家获得最高得分。

3 ≤ N ≤ 50 3\leq N \leq 50 3N50,
数据保证无论玩家如何操作,顶点上的数值均在 [ − 32768 , 32767 ] [−32768,32767] [32768,32767]之内。

在枚举第一步删除哪条边后,这道题就与“石子合并”非常相似,仍是在每一步中对两个相邻的元素做某种运算合成一个。简便起见,我们把被删除的边逆时针方向的顶点称为“第一个顶点”,依次类推。容易想到使用 F [ l , r ] F[l,r] F[l,r]表示把 l l l r r r个顶点合成一个顶点后,顶点上的数值最大是多少。

然而,在使用动态规划解决每一道问题时,都时刻牢记动态规划的“三要素”和使用动态规划的“三前提”。把“顶点上的最大数值”作为每一个子问题 [ l , r ] [l,r] [l,r]的代表信息,不符合动态规划的“最优子结构”性质。因为负数的存在,进行乘法运算时,大区间 [ l , r ] [l,r] [l,r]的顶点的最大数值不能由区间 [ l , k ] [l,k] [l,k]和区间 [ k + 1 , r ] [k+1,r] [k+1,r]合成的两个顶点的最大数值导出——因为区间 [ l , k ] [l,k] [l,k]和区间 [ k + 1 , r ] [k+1,r] [k+1,r]合成的两个顶点的最小数值可能是很小的负数,负负相乘得正,运算结果可能更大。

不过上面的反例也启发我们,如果把一个区间 [ l , r ] [l,r] [l,r]能够合成的顶点上的最大和最小数值同时作为子问题 [ l , r ] [l,r] [l,r]的代表信息,是否满足最优子结构性质?答案是肯定的。最大值的来源只可能是两个最大值相加、相乘,或是两个最小值相乘(负负得正),或一个最大值与一个最小值相乘(当其中一个子区间的最大、最小值都是正数,而另一个都是负数时)。最小值的来源只可能是两个最小值相加、相乘,或是两个最大值相乘,或一个最大值与一个最小值相乘。

因此,可以设 f [ l , r , 1 ] f[l,r,1] f[l,r,1]表示把第 l l l r r r个顶点合成一个顶点后,顶点上的数值最大是多少,设 f [ l , r , 0 ] f[l,r,0] f[l,r,0]表示把第 l l l r r r个顶点合成一个顶点后,顶点上的数值最小是多少。枚举区间的划分点 k k k(决策),状态转移方程如下:
f [ l ] [ r ] [ 1 ] = max ⁡ l ≤ k < r { f [ l ] [ k ] [ 1 ] + f [ k + 1 ] [ r ] [ 1 ] f [ l ] [ k ] [ p ] ∗ f [ k + 1 ] [ r ] [ q ] , p , q ∈ { 0 , 1 } f[l][r][1]=\underset{l\leq k <r}\max \left\{\begin{array}{l}f[l][k][1]+f[k+1][r][1] \\ f[l][k][p]*f[k+1][r][q],p,q\in\{0,1\} \end{array}\right. f[l][r][1]=lk<rmax{f[l][k][1]+f[k+1][r][1]f[l][k][p]f[k+1][r][q],p,q{0,1}

f [ l ] [ r ] [ 0 ] = min ⁡ l ≤ k < r { f [ l ] [ k ] [ 0 ] + f [ k + 1 ] [ r ] [ 0 ] f [ l ] [ k ] [ p ] ∗ f [ k + 1 ] [ r ] [ q ] , p , q ∈ { 0 , 1 } f[l][r][0]=\underset{l\leq k <r}\min \left\{\begin{array}{l}f[l][k][0]+f[k+1][r][0] \\ f[l][k][p]*f[k+1][r][q],p,q\in\{0,1\} \end{array}\right. f[l][r][0]=lk<rmin{f[l][k][0]+f[k+1][r][0]f[l][k][p]f[k+1][r][q],p,q{0,1}

初值: ∀ l ∈ [ 1 , N ] , F [ l , l , 0 ] = F [ l , l , 1 ] = A l \forall l \in [1,N],F[l,l,0]=F[l,l,1]=A_l l[1,N],F[l,l,0]=F[l,l,1]=Al,其余均为正或负无穷。

目标: F [ 1 , N , 1 ] F[1,N,1] F[1,N,1]

上述算法的时间复杂度为 O ( N 4 ) O(N^4) O(N4)。实际上我们还可以进一步优化掉枚举第一步删除哪条边耗费的时间。在游戏最初,我们选择一条边删掉,然后把剩下的“链”复制一倍接在末尾(以被删除的边逆时针方向的第一个顶点为开头),如下图所示:

在这里插入图片描述

在这个边长为 2 N 2N 2N的“链”上, ∀ i ∈ [ 1 , N ] \forall i\in [1,N] i[1,N]的区间 [ i , i + N − 1 ] [i,i+N-1] [i,i+N1]合并成一个顶点,就等价于原游戏的第一步删掉第 i i i个顶点逆时针一侧的边,然后把剩余的部分合并成一个顶点。因为区间长度是DP的阶段,我们只需要对前 N N N个阶段进行DP,每个阶段只有不超过 2 N 2N 2N个状态总时间复杂度降低为 O ( N 3 ) O(N^3) O(N3)。最后的答案是 max ⁡ 1 ≤ i ≤ N { F [ i , i + N − 1 ] } \underset{1\leq i\leq N} \max\{ F[i,i+N-1] \} 1iNmax{F[i,i+N1]}

#include <bits/stdc++.h>
using namespace std;int N;
int a[110];
char op[110];
int dp[110][110][2];int main()
{scanf("%d",&N);for(int i=1;i<=N;++i){getchar();scanf("%c %d",&op[i],&a[i]);}for(int i=N+1;i<=2*N;++i)op[i]=op[i-N],a[i]=a[i-N];for(int i=1;i<=2*N;++i)for(int j=1;j<=2*N;++j)dp[i][j][0]=0x3f3f3f3f,dp[i][j][1]=0xcfcfcfcf;for(int len=1;len<=N;++len){for(int l=1;l+len-1<=2*N;++l){int r=l+len-1;if(len==1){dp[l][r][0]=dp[l][r][1]=a[l];continue;}for(int k=l;k<r;++k){if(op[k+1]=='t'){dp[l][r][0]=min(dp[l][r][0],dp[l][k][0]+dp[k+1][r][0]);dp[l][r][1]=max(dp[l][r][1],dp[l][k][1]+dp[k+1][r][1]);}else{for(int i=0;i<2;++i)for(int j=0;j<2;++j){dp[l][r][0]=min(dp[l][r][0],dp[l][k][i]*dp[k+1][r][j]);dp[l][r][1]=max(dp[l][r][1],dp[l][k][i]*dp[k+1][r][j]);}}}}}int ans=0xcfcfcfcf;for(int i=1;i<=N;++i)ans=max(ans,dp[i][i+N-1][1]);printf("%d\n",ans);for(int i=1;i<=N;++i)if(ans==dp[i][i+N-1][1])printf("%d ",i);return 0;
}

这种“任意选择一个位置断开,复制形成两倍长度的链”的方法,是解决DP中环形结构的常用手段之一,我们会在0x55节进一步探讨。

虽然探索金字塔是极其老套的剧情,但是有一队探险家还是到了某金字塔脚下。

经过多年的研究,科学家对这座金字塔的内部结构已经有所了解。

首先,金字塔由若干房间组成,房间之间连有通道。

如果把房间看作节点,通道看作边的话,整个金字塔呈现一个有根树结构,节点的子树之间有序,金字塔有唯一的一个入口通向树根。

并且,每个房间的墙壁都涂有若干种颜色的一种。

探险队员打算进一步了解金字塔的结构,为此,他们使用了一种特殊设计的机器人。

这种机器人会从入口进入金字塔,之后对金字塔进行深度优先遍历。

机器人每进入一个房间(无论是第一次进入还是返回),都会记录这个房间的颜色。

最后,机器人会从入口退出金字塔。

显然,机器人会访问每个房间至少一次,并且穿越每条通道恰好两次(两个方向各一次), 然后,机器人会得到一个颜色序列。

但是,探险队员发现这个颜色序列并不能唯一确定金字塔的结构。

现在他们想请你帮助他们计算,对于一个给定的颜色序列,有多少种可能的结构会得到这个序列。

因为结果可能会非常大,你只需要输出答案对 1 0 9 10^9 109取模之后的值。

输入仅一行,包含一个字符串 S S S,长度不超过 300,表示机器人得到的颜色序列。

例如序列“ABABABA”对应5种金字塔结构,最底层是树根。我们认为子树之间是有序的,所以方案3和方案4是两种不同的方案。如下图所示。

在这里插入图片描述

0x21节中我们提到过,一棵树的每棵子树都对应着这棵树的DFS序中的一个区间。本题中记录的序列虽然不是DFS序,但仍满足这条性质。因此,这道题目在“树形结构”与“字符串”之间通过“子树”和“区间”建立了联系。结合本节前半部分对区间DP的分析,不难想到用 F [ l , r ] F[l,r] F[l,r]表示子串 S [ l ∼ r ] S[l\sim r] S[lr]对应着多少种可能得金字塔结构(树形结构)。

接下来我们考虑对区间的划分。以上图中的方案3为例,序列“ABABABA”被分成五个部分:

在这里插入图片描述

同理,方案5把序列分成“A|B|A|B|A|B|A”七个部分。也就是说,若子串 S [ l ∼ r ] S[l\sim r] S[lr]对应一棵子树,则 S [ l + 1 ] , S [ r − 1 ] S[l+1],S[r-1] S[l+1],S[r1]两个字符是进入和离开时产生的。除此之外, [ l , r ] [l,r] [l,r]包含的每棵更深的子树都对应着一个子问题,会产生 [ l , r ] [l,r] [l,r]中的一段。相邻两段之间还有途径树根产生的一个字符。因此, [ l , r ] [l,r] [l,r]包含的子树个数可能不止两个,如果我们像前面的题目一样,采用朴素算法枚举子串 S [ l ∼ r ] S[l\sim r] S[lr]划分点的数量和所有划分点的位置,那么时间复杂度会变得非常高。

把子串 S [ l ∼ r ] S[l\sim r] S[lr]分成两部分,每部分可由若干棵子树组成。不过这样可能会产生重复计数。如果每段可以由多颗子树构成,那么划分方案“A|BAB|A|B|A”和“A|B|A|BAB|A”中的”BAB“都能产生”B|A|B“两颗子树,最终归为同一结果——方案5。

实际上,为了解决让计数不重不漏,我们可以只考虑子串 S [ l ∼ r ] S[l\sim r] S[lr]第一棵子树是由哪一段构成的。枚举划分点 k k k,令子串 S [ l + 1 ∼ k − 1 ] S[l+1\sim k-1] S[l+1k1]构成 [ l , r ] [l,r] [l,r]的第一棵子树, S [ k ∼ r ] S[k\sim r] S[kr]构成 [ l , r ] [l,r] [l,r]的剩余部分(其他子树)。如下图所示。

在这里插入图片描述

如果 k k k不相同,那么子串 S [ l + 1 ∼ k − 1 ] S[l+1\sim k-1] S[l+1k1]代表的子树的大小也不相同,就不可能产生重复计算的结构。于是,我们可以得到状态转移方程:
F [ l , r ] = { 0 , S [ l ] ≠ S [ r ] ∑ l + 2 ≤ k ≤ r , S [ l ] = S [ k ] F [ l + 1 , k − 1 ] ∗ F [ k , r ] , S [ l ] = S [ r ] F[l,r]=\left \{\begin{array}{l} 0,S[l]\neq S[r] \\ \underset{l+2\leq k\leq r,S[l]=S[k]}\sum F[l+1,k-1]*F[k,r],S[l]=S[r] \end{array} \right. F[l,r]={0,S[l]=S[r]l+2kr,S[l]=S[k]F[l+1,k1]F[k,r],S[l]=S[r]
初值, ∀ l ∈ [ 1 , N ] , F [ l , l ] = 1 \forall l\in[1,N],F[l,l]=1 l[1,N],F[l,l]=1,其余均为0。目标: F [ 1 , N ] F[1,N] F[1,N]

这道题告诉我们,对于方案计数类的动态规划问题,通常一个状态的各个决策之间满足“加法原理”,而每个决策划分之间的几个子状态满足“乘法原理”。在设计状态转移方程的决策方案与划分方法时,一个状态的所有决策之间必须具有互斥性,才能保证不会出现重复问题。在0x5c节中我们会进一步探讨计数类DP的相关模型与求解策略。

#include <bits/stdc++.h>
using namespace std;char s[305];
int dp[305][305];
const int p=1e9;int main()
{scanf("%s",s+1);int N=strlen(s+1);for(int len=1;len<=N;++len){for(int l=1;l+len-1<=N;++l){int r=l+len-1;if(len==1){dp[l][r]=1;continue;}if(s[l]==s[r])for(int k=l+2;k<=r;++k)if(s[l]==s[k])dp[l][r]=(dp[l][r]+dp[l+1][k-1]*(long long)dp[k][r])%p;}}printf("%d",dp[1][N]);return 0;
}

在具体的程序编写中,区间DP不仅可以用递推(若干循环)来实现,也可以用递归(记忆化搜索)来实现。把子问题的求解过程写成一个函数 s o l v e ( l , r ) solve(l,r) solve(l,r),枚举划分点 k k k,递归求解 s o l v e ( l + 1 , k − 1 ) solve(l+1,k-1) solve(l+1,k1) s o l v e ( k , r ) solve(k,r) solve(k,r),回溯时把二者的结果相乘,加到 s o l v e ( l , r ) solve(l,r) solve(l,r)的结果中。在上述过程中,一个区间 [ l , r ] [l,r] [l,r]对应的函数 s o l v e ( l , r ) solve(l,r) solve(l,r)可能会被调用多次,我们可以建立一个全局函数 F F F,在第一次计算完 s o l v e ( l , r ) solve(l,r) solve(l,r)时把结果保存在 F [ l , r ] F[l,r] F[l,r]中,之后 s o l v e ( l , r ) solve(l,r) solve(l,r)再被调用时就可以直接返回 F [ l , r ] F[l,r] F[l,r]。这样带有记忆化的搜索就保证了每个区间只会求解一次,时间复杂度仍然是 O ( N 3 ) O(N^3) O(N3)

int f[305][305],p=1e9;
int solve(int l,int r)
{if(l>r) return 0;if(l==r) return 1;if(f[l][r]!=-1) return f[l][r]; //记忆化f[l][r]=0;for(int k=l+2;k<=r;++k)f[l][r]=(f[l][r]+(long long)solve(l+1,k-1)*solve(k,r))%p;return f[l][r];
}memset(f,-1,sizeof(f));
solve(1,N);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/611864.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python-面向对象

面向对象 1.初识对象1.1理解使用对象完成数据组织的思路 2.成员方法2.1类的定义和使用语法2.2成员方法的使用 3.类和对象4.构造方法4.1使用构造方法向成员变量赋值 5.其他内置方法5.1__str__字符串方法5.2__lt__小于符号比较方法5.3__le__小于等于比较符号5.4__eq__比较运算符实…

VUE3相比VUE2升级了哪些内容

目录 一、Vue 3 、Vue 2 对比及提升项 二、 Vue 3 创建app.vue示例 三、Vue3 的setup、Vue2 的 data对比 一、Vue 3 、Vue 2 对比及提升项 性能提升&#xff1a;Vue 3 做了大量的优化工作&#xff0c;提升了运行时的性能。例如&#xff0c;在模板编译时进行的静态分析和优化…

如何把苹果手机中的备忘录导入到电脑?这种方法特别方便

作为苹果手机的忠实用户&#xff0c;我深知备忘录在日常生活中的重要性。它是我随手记下的灵感闪现&#xff0c;是工作会议的要点记录&#xff0c;更是生活中的小提醒和重要事务的备忘录。然而&#xff0c;有时我需要将这些信息从手机导入到电脑中&#xff0c;以便更方便地查看…

k8s的存储卷---数据卷

前言 容器磁盘上的文件的生命周期是短暂的&#xff0c;这就使得在容器中运行重要应用时会出现一些问题。首先&#xff0c;当容器崩溃时&#xff0c;kubelet 会重启它&#xff0c;但是容器中的文件将丢失——容器以干净的状态&#xff08;镜像最初的状态&#xff09;重新启动。其…

【大数据OLAP引擎】StarRocks为什么快?

StarRocks的优势 StarRocks最初主要的优势是性能&#xff0c;当时在单表查询方面与性能标杆ClickHouse不相上下&#xff0c;而join优化特性使其在多表关联查询场景下的性能表现要远远优于ClickHouse&#xff0c;替换ClickHouse自然也就成了StarRocks的第一个目标。 而StarRoc…

three.js实现雷达扫描效果(纹理贴图)

three.js实现雷达扫描效果&#xff08;纹理贴图&#xff09; 图例 步骤 创建两个平面&#xff0c;分别纹理贴图&#xff0c;底图模型.add&#xff08;光波模型&#xff09;关闭材质的深度测试光波旋转 代码 <template><div class"app"><div ref&q…

【算法每日一练]-练习篇 #Tile Pattern #Swapping Puzzle # socks

目录 今日知识点&#xff1a; 二维前缀和 逆序对 袜子配对(感觉挺难的&#xff0c;又不知道说啥) Tile Pattern Swapping Puzzle socks Tile Pattern 331 题意&#xff1a;有一个10^9*10^9的方格。W表示白色方格&#xff0c;B表示黑色方格。每个(i,j)方的颜色由(i…

Unity真机Log工具 SRDebugger使用记录,GM布局管理

SRDebugger 官方文档安装及初始化常用设置选项布局选项快捷键选项高级设置 使用GM工具案例常用特性GM分组排序GM固定页签 官方文档 文档&#xff1a; https://www.stompyrobot.uk/tools/srdebugger/documentation/ 插件地址&#xff1a; https://assetstore.unity.com/package…

数据结构及单链表例题(下)

上次我们已经了解了单链表的数据结构定义以及创建单链表的两种方法,这节介绍几道例题. 文章目录 前言 一、已知L为带头结点的单链表,请依照递归思想实现下列运算 二、单链表访问第i个数据节点 三、在第i个元素前插入元素e 四、删除第i个结点 五、查找带头结点单链表倒数第…

TS 36.322 V12.0.0-过程

​本文的内容主要涉及TS 36.322&#xff0c;版本是C00&#xff0c;也就是V12.0.0。

构建安全可靠的系统:第十一章到第十五章

第三部分&#xff1a;实现系统 原文&#xff1a;Part III. Implementing Systems 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 一旦您分析并设计了您的系统&#xff0c;就该是实现计划的时候了。在某些情况下&#xff0c;实现可能意味着购买现成的解决方案。第十一章…

QT第1天

题目&#xff1a;点击按钮改变文字 需要增加一个count属性&#xff0c;并且只需要定义槽&#xff0c;信号函数已经内置好了 //widget.h#ifndef WIDGET_H #define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Wi…

[C++]多态

目录 C多态&#xff1a;&#xff1a; 多态的概念 多态的定义及实现 多态的构成条件 虚函数 虚函数的重写 虚函数重写的特例 C11 override和final 重载、重写重定义的对比 抽象类 概念 接口继承和实现继承 多态的原理 虚函数表 多态的原理 动态绑定和静态绑定 单继承和…

LeetCode 84:柱状图中的最大矩形

一、题目描述 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: 输入&#xff1a;heights [2,1,5,6,2,3] 输出&#xff1a;10 解释&#xff1a…

Jmeter+ant+Jenkins 接口自动化框架完整版

接口自动化测试单有脚本是不够的&#xff0c;我们还需要批量跑指定接口&#xff0c;生成接口运行报告&#xff0c;定位报错接口&#xff0c;接口定时任务&#xff0c;邮件通知等功能。批量跑指定接口&#xff1a;我们可以利用ant批量跑指定目录下的Jmeter脚本生成接口运行报告&…

vue3基础类型和引用类型,和store的使用

案例一&#xff1a; 如果我在store创建一个变量&#xff0c;是读取缓存key为name的数据&#xff0c; store.name 默认值是张三 # 声明一个变量 const title ref(store.name) # 然后修改title.value "李四"&#xff0c; # 问&#xff1a;打印store.name&#xff0…

怎么投稿各大媒体网站?

怎么投稿各大媒体网站&#xff1f;这是很多写作者及自媒体从业者经常面临的问题。在信息爆炸的时代&#xff0c;如何将自己的文章推送到广大读者面前&#xff0c;成为了一个不可避免的挑战。本文将为大家介绍一种简单有效的投稿方法——媒介库发稿平台发稿&#xff0c;帮助大家…

5,sharding-jdbc入门-sharding-jdbc广播表

执行sql #在数据库 user_db、order_db_1、order_db_2中均要建表 CREATE TABLE t_dict (dict_id BIGINT (20) NOT NULL COMMENT 字典id,type VARCHAR (50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 字典类型,code VARCHAR (50) CHARACTER SET utf8 COLLAT…

国产AI工具钉钉AI助理:开启个性化助手服务的新篇章

钉钉AI助理是钉钉平台的一项功能&#xff0c;它可以根据用户的需求提供个性化的AI助手服务。用户可以在AI助理页面一键创建个性化的AI助理&#xff0c;如个人的工作AI助理、旅游AI助理、资讯AI助理等。企业也可以充分使用企业所沉淀的知识库和业务数据&#xff0c;在获得授权后…

C#入门篇(一)

变量 顾名思义就是变化的容器&#xff0c;即可以用来存放各种不同类型数值的一个容器 折叠代码 第一步&#xff1a;#region 第二步&#xff1a;按tab键 14种数据类型 有符号的数据类型 sbyte&#xff1a;-128~127 short&#xff1a;-32768~32767 int&#xff1a;-21亿多~21亿多…