动态规划 之 区间DP练习
前言
\(Loj\) 放上了那么多《信息学奥赛一本通》上的题(虽然我并没有这本书),我要给它点一个大大的赞 _
以后分类刷题不愁啦!
正文
那就一道道说吧。
石子合并
将 \(n\) 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。
求 \(n−1\) 次合并后得分总和的最大值和最小值。
\(n \leq 200\)
首先注意到“绕圆形排放”,那么有一个经典的技巧就是在 \(1 \sim n\) 后再接一个 \(1 \sim n\) ,相当于把圆拆成一条直线
接着就是经典的区间 \(DP\) ,方程 \(f[i][j]=\mathop{\max}\limits_{k=i}^{j-1} \{f[i][k]+f[k+1][j]+sum[i][j] \}\),最小值亦然
\(sum[i][j]\)为第 \(i\) 堆到第 \(j\) 堆石子数之和,可以用前缀和算出来。
注意转移的顺序为区间大小从小至大。
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 405;
int n;
int f[N][N],g[N][N],s[N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&f[i][i]),f[i+n][i+n]=f[i][i];
for(int i=1;i<=n*2;i++)
s[i]=s[i-1]+f[i][i],f[i][i]=0;
for(int i=2;i<=n;i++)
for(int j=1;j+i-1<=n*2;j++){
g[j][i+j-1]=2*1e9;
for(int k=j;k<j+i-1;k++){
f[j][i+j-1]=max(f[j][i+j-1],f[j][k]+f[k+1][i+j-1]+s[i+j-1]-s[j-1]);
g[j][i+j-1]=min(g[j][i+j-1],g[j][k]+g[k+1][i+j-1]+s[i+j-1]-s[j-1]);
}
}
int Max=0,Min=2*1e9;
for(int i=1;i<=n;i++)
Max=max(Max,f[i][i+n-1]),Min=min(Min,g[i][i+n-1]);
printf("%d\n%d",Min,Max);
return 0;
}
能量项链
在项链上有 \(N\) 颗能量珠。能量珠是一颗有头标记和尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记必定等于后一颗珠子的头标记。如果一颗能量珠头标记为 \(m\),尾标记为 \(r\),后一颗能量珠头标记为 \(r\),尾标记为 \(n\),则聚合后释放出 \(m \times r \times n\) 单位的能量,新珠子头标记为 \(m\),尾标记为 \(n\) 。
项链上有 \(n\) 颗珠子,相邻两颗珠子可以合并成一个,合并同时会放出一定的能量,不同珠子合并放出能量不相同,请问按怎样的次序合并才能使得释放的能量最多?
\(n \leq 100\)
与第一题套路差不多,这个题所有能量珠也是围成一个圈的(吐槽一下,这个事情原题中说的很不清楚)
所以也是 \(1\sim n\) 后再接 \(1 \sim n\)
转移方程也很套路,\(f[i][j]=\mathop{\max}\limits_{k=i}^{j-1} \{ f[i][k]+f[k+1][j]+w[i] \times w[k] \times w[j] \}\)
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 205;
typedef long long ll;
int n;
ll w[N],f[N][N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&w[i]),w[i+n]=w[i];
for(int i=3;i<=n+1;i++)
for(int j=1;j+i-1<=2*n;j++)
for(int k=j+1;k<j+i-1;k++)
f[j][j+i-1]=max(f[j][j+i-1],f[j][k]+f[k][j+i-1]+w[j]*w[j+i-1]*w[k]);
ll ans=0;
for(int i=1;i<=n;i++) ans=max(ans,f[i][i+n]);
printf("%lld\n",ans);
return 0;
}
凸多边形的划分
给定一个具有 \(N\) 个顶点的凸多边形,将顶点从 \(1\) 至 \(N\) 标号,每个顶点的权值都是一个正整数。将这个凸多边形划分成 \(N−2\) 个互不相交的三角形,试求这些三角形顶点的权值乘积和至少为多少。
\(N \leq 50\) ,每个点权值 \(\leq 10^9\)
这是一道好题!!(因为我居然想了好半天没想出来,最后看的题解【捂脸】)
之前没想到是因为不知道怎么让区间连续起来,有些三角形三个顶点都不连续……
后来才意识到,当前多边形每一条边都一定在一个三角形中,故只需要拿住一条边,再选一个点作为三角形另一个顶点,然后就会把多边形分为两个部分,而这两个部分的多边形顶点是连续的。好巧好巧!
多边形定点是连成一个圈的,故也要在 \(1 \sim n\) 后接 \(1 \sim n\)
方程 \(f[i][j]=\mathop{\max}\limits_{k=i+1}^{j-1} \{ f[i][k]+f[k][j]+w[i] \times w[k] \times w[j] \}\)
坑人的是要写高精度 \(qwq\)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 55*2;
const int SZ =1000;
struct Bign{
int s[15],len;
Bign() { len=0; memset(s,0,sizeof(s)); }
void print(){
printf("%d",s[len-1]);
for(int i=len-2;i>=0;i--) {
if(s[i]<10) printf("0");
if(s[i]<100) printf("0");
printf("%d",s[i]);
}
printf("\n");
}
Bign operator = (int x) {
if(x==0) s[len++]=0;
while(x!=0) s[len++]=x%SZ,x/=SZ;
return *this;
}
Bign operator + (const Bign &b) const {
Bign c;
for(int i=0,g=0;;i++){
if(i>=len && i>=b.len && g==0) break;
c.s[c.len++]=(s[i]+b.s[i]+g)%SZ;
g=(s[i]+b.s[i]+g)/SZ;
}
return c;
}
Bign operator * (const Bign &b) const {
Bign c;
for(int i=0;i<len;i++)
for(int j=0;j<b.len;j++) c.s[i+j]+=s[i]*b.s[j];
c.len=len+b.len-1; /**/
for(int i=0,g=0;;i++){
if(i>=c.len && c.s[i]==0 && g==0) { c.len=i; break; }
g=c.s[i]+g;
c.s[i]=g%SZ;
g/=SZ;
}
return c;
}
bool operator > (const Bign &b) const {
if(len!=b.len) return len>b.len;
for(int i=len-1;i>=0;i--)
if(s[i]!=b.s[i]) return s[i]>b.s[i];
return false;
}
}w[N],f[N][N],MAX;
int n;
int main()
{
int x;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&x),w[i]=x,w[i+n]=x;
MAX.len=15; MAX.s[14]=1;
for(int i=1;i<n*2;i++) f[i][i+1]=(x=0);
for(int i=3;i<=n;i++)
for(int j=1;j+i-1<=n*2;j++){
f[j][i+j-1]=MAX;
for(int k=j+1;k<j+i-1;k++)
if(f[j][i+j-1]>f[j][k]+f[k][i+j-1]+w[j]*w[k]*w[i+j-1])
f[j][i+j-1]=f[j][k]+f[k][i+j-1]+w[j]*w[k]*w[i+j-1];
}
Bign m=MAX;
for(int i=1;i<=n;i++) if(m>f[i][i+n-1]) m=f[i][i+n-1];
m.print();
return 0;
}
括号配对
\(BE\) 中有一类被称为 \(GBE\)。
以下是 \(GBE\) 的定义:
1.空表达式是 \(GBE\)
2.如果表达式 \(A\) 是 \(GBE\),则 \([A]\) 与 \((A)\) 都是 \(GBE\)
3.如果 \(A\) 与 \(B\) 都是 \(GBE\),那么 \(AB\) 是 \(GBE\)
给定 \(BE\) ,问最少添加多少字符可将其变为 \(GBE\)
字符串长度小于100
这个就是很常规的区间\(DP\),感觉没什么好说的
对于 \(f[i][j]\) ,先讨论 \(s[i]\) 与 \(s[j]\) 是否可凑成一对中括号或小括号,如果可以的话 \(f[i][j]=f[i+1][j-1]\)
接着 \(f[i][j]=\mathop{\max}\limits_{k=i}^{j-1} \{ f[i][k]+f[k+1][j]\}\)
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#define INF 100007
using namespace std;
const int N = 105;
int n;
int f[N][N];
char s[N];
int main()
{
scanf("%s",s);
n=strlen(s);
for(int i=0;i<n;i++)
if(s[i]=='(' || s[i]==')' || s[i]=='[' || s[i]==']') f[i][i]=1;
for(int i=2;i<=n;i++)
for(int j=0;j+i-1<n;j++){
f[j][i+j-1]=INF;
if(s[j]=='(' && s[i+j-1]==')') f[j][i+j-1]=min(f[j][i+j-1],f[j+1][i+j-2]);
if(s[j]=='[' && s[i+j-1]==']') f[j][i+j-1]=min(f[j][i+j-1],f[j+1][i+j-2]);
for(int k=j;k<i+j-1;k++)
f[j][i+j-1]=min(f[j][i+j-1],f[j][k]+f[k+1][i+j-1]);
}
printf("%d\n",f[0][n-1]);
return 0;
}
分离与合体
原题太长了我要吐槽!!!
杜神牛造了 \(n\) 个区域,他们紧邻着排成一行,编号 \(1…n\) 。在每个区域里都放着一把 \(OI\) 界的金钥匙,每一把都有一定的价值。
一开始 \(LYD\) 可以选择 \(1…n−1\) 中的任何一个区域进入,我们不妨把这个区域记为 \(k\)。进入后 \(LYD\) 会在 \(k\) 区域发生分离,从而分离成两个小 \(LYD\)。分离完成的同时会有一面墙在 \(k\) 区域和 \(k+1\) 区域间升起,从而把 \(1…k\) 和 \(k+1…n\) 阻断成两个独立的区间,并在各自区间内任选除区间末尾之外的任意一个区域再次发生分离,这样就有了四个小小 \(LYD\)……重复以上所叙述的分离,直到每个小 \(LYD\) 发现自己所在的区间只剩下了一个区域,那么他们就可以抱起自己梦寐以求的 \(OI\) 金钥匙。
但是 \(LYD\) 不能就分成这么多个个体存在于世界上,这些小 $ LYD$ 还会再合体,合体的小 $ LYD$ 所在区间中间的墙会消失。合体会获得 (合并后所在区间左右端区域里金钥匙价值之和)\(\times\) (之前分离的时候所在区域的金钥匙价值)。
\(LYD\) 请你编程求出最终可以获得的最大总价值,并按照分离阶段从前到后,区域从左到右的顺序,输出发生分离区域编号。若有多种方案,选择分离区域尽量靠左的方案(也可以理解为输出字典序最小的)。
\(n \leq 300\)
感觉这题难点在于耐下心把题看完……
方程 \(f[i][j]=\mathop{\max}\limits_{k=i}^{j-1} \{ f[i][k]+f[k+1][j]+(a[i]+a[j]) \times a[k] \}\)
因为最后要输出方案,所以每个区域要记录下使 \(f[i][j]\) 取到 \(max\) 的 \(k\)
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 305;
typedef long long ll;
typedef pair<int,int> P;
int n;
ll a[N],f[N][N];
int g[N][N];
int head,tail;
P que[N*N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
for(int i=2;i<=n;i++)
for(int j=1;j+i-1<=n;j++)
for(int k=j;k<j+i-1;k++)
if(f[j][i+j-1]<f[j][k]+f[k+1][i+j-1]+(a[j]+a[i+j-1])*a[k])
f[j][i+j-1]=f[j][k]+f[k+1][i+j-1]+(a[j]+a[i+j-1])*a[k],g[j][i+j-1]=k;
printf("%lld\n",f[1][n]);
que[tail++]=P(1,n);
while(head<tail){
int x=que[head].first,y=que[head++].second;
if(x==y) continue;
if(x!=1 || y!=n) printf(" %d",g[x][y]);
else printf("%d",g[x][y]);
que[tail++]=P(x,g[x][y]);
que[tail++]=P(g[x][y]+1,y);
}
return 0;
}
矩阵取数游戏
对于给定的 \(n \times m\) 的矩阵,矩阵中每个元素 \(a_{i,j}\) 均为非负整数。游戏规则如下:
1.每次取数时必须从每行各取走一个元素,共 \(n\) 个,\(m\) 次取完所有元素。
2.每次取走的各个元素只能是该元素所在行行首或行尾。
3.每次取数都有一个的分值,为每行取数得分之和,每行取数得分=被取走元素值 \(\times 2^i\),其中 \(i\) 表示第 \(i\) 次取数,从 1 开始计数。
4.游戏结束时,总得分为 \(m\) 次取数得分之和。
求取数后的最大得分。\(n,m \leq 80, a_{i,j} \leq 10^3\)
这个也没啥难的,每行一次区间\(dp\)
方程 \(f[i][j]=max \{ f[i][j-1] +a[j] \times 2^{m-(j-i+1)+1} , f[i+1][j] +a[i] \times 2^{m-(j-i+1)+1} \}\)
恶心的是又要加高精度。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 85;
const int SZ = 10000;
struct Bign{
int s[10],len;
Bign() { len=0; memset(s,0,sizeof(s)); }
Bign operator = (int x){
len=0; memset(s,0,sizeof(s));
if(x==0) s[len++]=0;
while(x!=0) s[len++]=x%SZ,x/=SZ;
return *this;
}
Bign operator + (const Bign &b) const{
Bign c;
for(int i=0,g=0;;i++){
if(i>=len && i>=b.len && g==0) break;
c.s[c.len++]=(s[i]+b.s[i]+g)%SZ;
g=(s[i]+b.s[i]+g)/SZ;
}
return c;
}
Bign operator * (const int &x) const{
Bign c;
for(int i=0,g=0;;i++){
if(i>=len && g==0) { c.len=i; break; }
g=g+x*s[i];
c.s[i]=g%SZ;
g/=SZ;
}
return c;
}
bool operator < (const Bign &b) const{
if(len!=b.len) return len<b.len;
for(int i=len-1;i>=0;i--)
if(s[i]!=b.s[i]) return s[i]<b.s[i];
return false;
}
void print(){
printf("%d",s[len-1]);
for(int i=len-2;i>=0;i--){
if(s[i]<10) printf("0");
if(s[i]<100) printf("0");
if(s[i]<1000) printf("0");
printf("%d",s[i]);
}
printf("\n");
}
}mod[N],f[N][N],ans;
int n,m;
int a[N];
int main()
{
scanf("%d%d",&n,&m);
mod[0]=1;
for(int i=1;i<=m;i++) mod[i]=mod[i-1]*2;
ans=0;
for(int x=1;x<=n;x++){
for(int y=1;y<=m;y++)
scanf("%d",&a[y]),f[y][y]=mod[m]*a[y];
for(int i=2;i<=m;i++)
for(int j=1;j+i-1<=m;j++){
f[j][i+j-1]=0;
if(f[j][i+j-1]<f[j][i+j-2]+mod[m-i+1]*a[i+j-1])
f[j][i+j-1]=f[j][i+j-2]+mod[m-i+1]*a[i+j-1];
if(f[j][i+j-1]<f[j+1][i+j-1]+mod[m-i+1]*a[j])
f[j][i+j-1]=f[j+1][i+j-1]+mod[m-i+1]*a[j];
}
ans=ans+f[1][m];
}
ans.print();
return 0;
}
动态规划 之 区间DP练习的更多相关文章
- 动态规划(区间DP):HDU 5115 Dire Wolf
Dire wolves, also known as Dark wolves, are extraordinarily large and powerful wolves. Many, if not ...
- 动态规划:区间DP与环形DP
区间型动态规划的典型例题是石子归并,同时使用记忆化搜索实现区间动归是一种比较容易实现的方式,避免了循环数组实现的时候一些边界的判断 n堆石子排列成一条线,我们可以将相邻的两堆石子进行合并,合并之后需要 ...
- 动态规划——区间dp
在利用动态规划解决的一些实际问题当中,一类是基于区间上进行的,总的来说,这种区间dp是属于线性dp的一种.但是我们为了更好的分类,这里仍将其单独拿出进行分析讨论. 让我们结合一个题目开始对区间dp的探 ...
- 动态规划——区间DP,计数类DP,数位统计DP
本博客部分内容参考:<算法竞赛进阶指南> 一.区间DP 划重点: 以前所学过的线性DP一般从初始状态开始,沿着阶段的扩张向某个方向递推,直至计算出目标状态. 区间DP也属于线性DP的一种, ...
- Codeforces Gym100543L Outer space invaders 区间dp 动态规划
原文链接https://www.cnblogs.com/zhouzhendong/p/CF-Gym100543L.html 题目传送门 - CF-Gym100543L 题意 $T$ 组数据. 有 $n ...
- ACM学习历程—HDU1584 蜘蛛牌(动态规划 && 状态压缩 || 区间DP)
Description 蜘蛛牌是windows xp操作系统自带的一款纸牌游戏,游戏规则是这样的:只能将牌拖到比她大一的牌上面(A最小,K最大),如果拖动的牌上有按顺序排好的牌时,那么这些牌也跟着一起 ...
- 模板 - 动态规划 - 区间dp
因为昨天在Codeforces上设计的区间dp错了(错过了上紫的机会),觉得很难受.看看学长好像也有学,就不用看别的神犇的了. 区间dp处理环的时候可以把序列延长一倍. 下面是 $O(n^3)$ 的朴 ...
- 动态规划---区间dp
今天写内网题,连着写了两道区间dp,这里就总结一下. 区间dp思想主要是先枚举f[i][j]中的i,再枚举j,再枚举一个1~j之间的变量k,一般是f[i][j] = max(f[i][j],f[i][ ...
- [hdu contest 2019-07-29] Azshara's deep sea 计算几何 动态规划 区间dp 凸包 graham扫描法
今天hdu的比赛的第一题,凸包+区间dp. 给出n个点m个圆,n<400,m<100,要求找出凸包然后给凸包上的点连线,连线的两个点不能(在凸包上)相邻,连线不能与圆相交或相切,连线不能相 ...
随机推荐
- Linux 内核中的数据类型
在我们进入更高级主题之前, 我们需要停下来快速关注一下可移植性问题. 现代版本的 Linux 内核是 高度可移植的, 它正运行在很多不同体系上. 由于 Linux 内核的多平台特性, 打算做认真使用的 ...
- vue-learning:20 - js - 区别:filters / data / computed / watch / methods
区别:filters / data / computed / watch / methods 在配置对象options中,filters/data/computed/watch/methods的每一项 ...
- [Python之路] 内存管理&垃圾回收
一.python源码 1.准备源码 下载Python源码:https://www.python.org/ftp/python/3.8.0/Python-3.8.0.tgz 解压得到文件夹: 我们主要关 ...
- 缓存, 队列(Redis,RabbitMQ)
Redis Redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorte ...
- 006 管理Ceph的RBD块设备
一, Ceph RBD的特性 支持完整和增量的快照 自动精简配置 写时复制克隆 动态调整大小 二.RBD基本应用 2.1 创建RBD池 [root@ceph2 ceph]# ceph osd pool ...
- rest_framework框架下的Django声明和生命周期
rest_framework框架下的Django声明和生命周期 Django声明周期(request) 客户端发起请求 请求经过wsgi wsgi: 是一个协议----web服务网关接口,即在web服 ...
- $bzoj4722$ 由乃 搜索
正解:搜索 解题报告: 传送门$QwQ$ 首先发现长度为$len$的子集的值域为$[0,v\cdot len+len]$,数量为$2^{len}$.所以当$2^{len}\geq v\cdot len ...
- 浅析Java hashCode()方法
散列码(hash code)是由对象导出的一个整数值. 散列码没有规律,两个不同的对象x和y,x.hashCode()与y.hashCode()基本上不会相同. public static voi ...
- SQL预处理
每向数据库发送一条SQL语句,数据库中的SQL解释器就会将SQL语句转换成数据库底层命令,然后执行该命令完成相关的数据库操作.如果频繁的向数据库提交SQL语句,势必会增加数据库中SQL解释器的负担,进 ...
- UI自动化和selenium相关以及八大定位
一.UI自动化相关 1. UI自动化的本质(重点) 定位元素→操作元素→模拟页面操作→断言→测试报告 2. 适合UI自动化的场景 UI自动化的前提条件 (1)需求不能频繁变动 (2)UI稳定(UI自动 ...