前言

\(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练习的更多相关文章

  1. 动态规划(区间DP):HDU 5115 Dire Wolf

    Dire wolves, also known as Dark wolves, are extraordinarily large and powerful wolves. Many, if not ...

  2. 动态规划:区间DP与环形DP

    区间型动态规划的典型例题是石子归并,同时使用记忆化搜索实现区间动归是一种比较容易实现的方式,避免了循环数组实现的时候一些边界的判断 n堆石子排列成一条线,我们可以将相邻的两堆石子进行合并,合并之后需要 ...

  3. 动态规划——区间dp

    在利用动态规划解决的一些实际问题当中,一类是基于区间上进行的,总的来说,这种区间dp是属于线性dp的一种.但是我们为了更好的分类,这里仍将其单独拿出进行分析讨论. 让我们结合一个题目开始对区间dp的探 ...

  4. 动态规划——区间DP,计数类DP,数位统计DP

    本博客部分内容参考:<算法竞赛进阶指南> 一.区间DP 划重点: 以前所学过的线性DP一般从初始状态开始,沿着阶段的扩张向某个方向递推,直至计算出目标状态. 区间DP也属于线性DP的一种, ...

  5. Codeforces Gym100543L Outer space invaders 区间dp 动态规划

    原文链接https://www.cnblogs.com/zhouzhendong/p/CF-Gym100543L.html 题目传送门 - CF-Gym100543L 题意 $T$ 组数据. 有 $n ...

  6. ACM学习历程—HDU1584 蜘蛛牌(动态规划 && 状态压缩 || 区间DP)

    Description 蜘蛛牌是windows xp操作系统自带的一款纸牌游戏,游戏规则是这样的:只能将牌拖到比她大一的牌上面(A最小,K最大),如果拖动的牌上有按顺序排好的牌时,那么这些牌也跟着一起 ...

  7. 模板 - 动态规划 - 区间dp

    因为昨天在Codeforces上设计的区间dp错了(错过了上紫的机会),觉得很难受.看看学长好像也有学,就不用看别的神犇的了. 区间dp处理环的时候可以把序列延长一倍. 下面是 $O(n^3)$ 的朴 ...

  8. 动态规划---区间dp

    今天写内网题,连着写了两道区间dp,这里就总结一下. 区间dp思想主要是先枚举f[i][j]中的i,再枚举j,再枚举一个1~j之间的变量k,一般是f[i][j] = max(f[i][j],f[i][ ...

  9. [hdu contest 2019-07-29] Azshara's deep sea 计算几何 动态规划 区间dp 凸包 graham扫描法

    今天hdu的比赛的第一题,凸包+区间dp. 给出n个点m个圆,n<400,m<100,要求找出凸包然后给凸包上的点连线,连线的两个点不能(在凸包上)相邻,连线不能与圆相交或相切,连线不能相 ...

随机推荐

  1. linux 让出处理器

    如我们已见到的, 忙等待强加了一个重负载给系统总体; 我们乐意找出一个更好的技术. 想到的第一个改变是明确地释放 CPU 当我们对其不感兴趣时. 这是通过调用调度函数而 实现地, 在 <linu ...

  2. C++ 驱动开发 error LNK2019

    最近在写一个机器人的时候,发现驱动无法编译通过.本文告诉大家如何解决这个问题. 在 VisualStudio 2017 15.8 的版本提供新的功能C++ Just My Code Stepping ...

  3. 北京师范大学第十七届程序设计竞赛决赛 G

    传送门:https://ac.nowcoder.com/acm/contest/895/G 题意: \[ 操作 1:L,R,X,Y,对所有L≤i≤R赋值 \\ Ai=min(Ai,(i−L)×Y+X) ...

  4. Servlet 会话

    在网络的七层模型中,会话层位于传输层之上,它定义如何开始.控制和结束一个会话.七层模式目前仅仅处于理论阶段,但是Web中借鉴了其中的一些思路.在Web中浏览器第一次发送请求到服务器开始直到一方断开为止 ...

  5. sql查询分类和所有子类

    select * from [JianDu].[dbo].[ZuZhiJiGou] where id = 64 --查询节点 union allSELECT TOP 1000 a.* FROM [Ji ...

  6. JAVA8学习——深入浅出Lambda表达式(学习过程)

    JAVA8学习--深入浅出Lambda表达式(学习过程) lambda表达式: 我们为什么要用lambda表达式 在JAVA中,我们无法将函数作为参数传递给一个方法,也无法声明返回一个函数的方法. 在 ...

  7. Java中的Redis 哨兵高可用性

    让我们探索Redis Sentinel,看看如何在Java上运行它,一起来看看,最近get了很多新知识,分享给大家参考学习.需要详细的java架构思维导图路线也可以评论获取! 什么是Redis哨兵? ...

  8. requests爬取梨视频主页所有视频

    爬取梨视频步骤: 1.爬取梨视频主页,获取主页所有的详情页链接 - url: https://www.pearvideo.com/ - 1) 往url发送请求,获取主页的html文本 - 2) 解析并 ...

  9. 20191031-7 beta week 1/2 Scrum立会报告+燃尽图 05

    此作业的要求参见:https://edu.cnblogs.com/campus/nenu/2019fall/homework/9915 git地址:https://e.coding.net/Eusti ...

  10. 大学最新毕业论文参考文献,包含java,jsp,mysql,Android,sql,PHP

    每当毕业论文写到最后需要参考文献时,往往是很令人头疼的,因为有的老师对参考文献的要求是很多的,比如需要国内的和国外的,时间必须是近三年的,满足XXX要求的文献至少需要三篇以上等等.今天我就来给大家整理 ...