引入
一个这样的问题,给定
n
n
n 个整数(可能为负)
a
1
…
a
n
a_1\ldots a_n
a1…an ,从中选出若干个数使其总和最大。
这明显就是贪心,正数就选,其余不管。
那如果增加一个规则,不能选相邻的两个数,就不能再贪心了。
比如:
−
1
−
5
4
2147483647
−
2
-1\quad -5\quad 4\quad 2147483647\quad -2
−1−542147483647−2
正常贪心在遇到
4
4
4 时就选了,但是这样就选不到后面极大的
2147483647
2147483647
2147483647 了。
这时候就需要在局部最优的同时顾全大局,那么就有了动态规划。
动态规划
动态规划,就是所谓的 DP 。
在动态规划中,一个问题的最优解是根据这个问题的子问题的最优解来处理的。
线性DP的分类
- 坐标类DP
- 多维DP(其实包含背包和区间,但主要要讲的是三维+)
- 背包问题
- 区间DP
不是没有了,而是这篇文章只讲这些。
坐标类DP
遇到数轴类问题,求到达 n n n 点是的最小代价或者最大收获。
例题1
题目描述
给定一个非负整数数组 nums ,你最初位于数组的第一个位置 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达数组的最后一个位置。
输入格式
输入有多组测试数据
每组测试数据输入有两行
第一行为一个整数n,表示nums数组元素数量
第二行有n个数字,分别表示num数组的每个元素
输出格式
对于每组测试数据,输出只有一行,如果可以到达最后一个下标,则输出:Yes,否则输出No。
动态规划,用一个数组 d p dp dp, d p i dp_i dpi 表示能否跳到第 i i i 个点,然后我们可以根据 d p 1 … n − 1 dp_{1\ldots n\!-\!1} dp1…n−1 来推出最后的 d p n dp_n dpn,也就是“是否能跳到点 n n n”的询问。
首先,如果第 i i i 个点往后跳 n u m s i nums_i numsi 已经大于等于 n n n,那么这个点一定能跳到终点,这一点是必然的。
接着,如果第 i i i 个点往后跳 1 , 2 , … , n u m s i 1,2,\ldots,nums_i 1,2,…,numsi 个点,跳到了一个可以跳到终点的点,那么这个点也能跳到终点。
那么就有:
if(i+nums[i]>=n)dp[i]=1;
else for(int j=i+1;j<=i+nums[i];j++)dp[i]|=dp[j];
由于靠后的位置更有可能到达终点,所以倒着循环
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n;
int nums[N];
int dp[N];
signed main(){
ios::sync_with_stdio(0);
while(cin>>n){
for(int i=1;i<=n;i++)cin>>nums[i],dp[i]=0;
for(int i=n;i>=1;i--){
if(i+nums[i]>=n)dp[i]=1;
else for(int j=i+1;j<=i+nums[i];j++)dp[i]|=dp[j];
}
if(dp[1])cout<<"Yes\n";
else cout<<"No\n";
}
}
例题2
题目描述
人们决定把他们的房子都涂成红色,绿色或蓝色,同时要求不能把两个相邻的房子涂成同样的颜色。一共有N栋房子,编号从1~N。房子顺序排成一行,因此编号为i房子的邻居是房子i-1和i+1。第一栋和最后一栋房子不是邻居。
每栋房子涂成红色,绿色或蓝色的花费是各不相同的。
给出费用信息,求出把所有房子涂上颜色的最小总花费。
输入格式
第1行:1个整数N,表示房子的数量
(
3
≤
N
≤
5000
)
(3\le N\le5000)
(3≤N≤5000)。
接下来N行,每行3个整数,表示涂成红色,绿色和蓝色的花费。
1
≤
1\le
1≤ 单个费用
≤
1000
\le1000
≤1000。
输出格式
第1行:1个整数,表示最小总花费。
我们把红绿蓝三种颜色分别表示为 0 0 0, 1 1 1 和 2 2 2,用 d p i j dp_{ij} dpij表示第 i i i 个房屋选用了第 j j j 种颜色。
由于相邻房屋不能涂相同颜色,所以
d
p
i
j
dp_{ij}
dpij 的值就只能由
d
p
i
k
(
0
≤
k
≤
2
,
k
!
=
j
)
dp_{ik(0\le k\le2,k!=j)}
dpik(0≤k≤2,k!=j) 推导而来。
所以就有:
dp[0][i]=min(dp[1][i-1],dp[2][i-1])+red[i];
dp[1][i]=min(dp[0][i-1],dp[2][i-1])+green[i];
dp[2][i]=min(dp[0][i-1],dp[1][i-1])+blue[i];
没有其他的限制,所以直接从第 1 1 1 个房屋推到第 n n n 个房屋。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
int n;
int red[N],green[N],blue[N];
int dp[3][N];
signed main(){
n=read();
for(int i=1;i<=n;i++)red[i]=read(),green[i]=read(),blue[i]=read();
for(int i=1;i<=n;i++){
dp[0][i]=min(dp[1][i-1],dp[2][i-1])+red[i];
dp[1][i]=min(dp[0][i-1],dp[2][i-1])+green[i];
dp[2][i]=min(dp[0][i-1],dp[1][i-1])+blue[i];
}
print(min({dp[0][n],dp[1][n],dp[2][n]}));
}
例题3
房屋染色II
先想想朴素思路,枚举房屋
O
(
N
)
O(N)
O(N),枚举颜色
O
(
K
)
O(K)
O(K),每个颜色取最小值
O
(
K
)
O(K)
O(K),总时间复杂度
O
(
N
K
2
)
O(NK^2)
O(NK2)。
但是,我们每一次都是取上一个房屋的最小值,所以可以在上一次提前处理好最小值,但是可能遇到上次的最小值与这一次选择的颜色一致,所以可以再取一个次小值。
if(j!=mni)dp[i][j]=dp[i-1][mni]+a[i][j];
else dp[i][j]=dp[i-1][cmn]+a[i][j];
然后在计算第 i i i 间房屋前先把最小值和次小值处理好,就行了。
#include<bits/stdc++.h>
using namespace std;
const int M=1e4+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
int n,k;
int a[M][M];
int dp[M][M];
signed main(){
n=read(),k=read();
if(k==1&&n>1){
print(-1);
return 0;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=k;j++){
a[i][j]=read();
}
}
for(int j=1;j<=k;j++){
dp[1][j]=a[1][j];
}
for(int i=2;i<=n;i++){
int mni=1,cmn=2;
if(dp[i-1][1]>dp[i-1][2]){
swap(mni,cmn);
}
for(int j=3;j<=k;j++){
if(dp[i-1][j]<dp[i-1][mni]){
cmn=mni;
mni=j;
}
else if(dp[i-1][j]<dp[i-1][cmn]){
cmn=j;
}
}
for(int j=1;j<=k;j++){
if(j!=mni)dp[i][j]=dp[i-1][mni]+a[i][j];
else dp[i][j]=dp[i-1][cmn]+a[i][j];
}
}
int mn=dp[n][1];
for(int i=2;i<=k;i++){
if(dp[n][i]<mn){
mn=dp[n][i];
}
}
print(mn);
}
多维DP
在二维的坐标系上或区间上多个状态的DP
例题
P1541 [NOIP 2010 提高组] 乌龟棋
由于有四张卡片,我们考虑用
d
p
a
b
c
d
dp_{abcd}
dpabcd 表示用了
a
a
a 张卡片
1
1
1,
b
b
b 张卡片
2
2
2,
c
c
c 张卡片
3
3
3,
d
d
d 张卡片
4
4
4。
当使用了 a a a 张卡片 1, b b b 张卡片 2, c c c 张卡片 3, d d d 张卡片 4 的时候,会向前走 a + 2 b + 3 c + 4 d a+2b+3c+4d a+2b+3c+4d 格,也就是说使用完后停留在第 a + 2 b + 3 c + 4 d + 1 a+2b+3c+4d+1 a+2b+3c+4d+1 格,也会获得对应格子的分数。
到达某个格子时,可能是使用了 1 1 1 到 4 4 4 中任意一张卡片到的,而这一格的加分是固定的,所以需要之前的格子尽可能大,那么就有:
if(a)dp[a][b][c][d]=max(dp[a][b][c][d],dp[a-1][b][c][d]);
if(b)dp[a][b][c][d]=max(dp[a][b][c][d],dp[a][b-1][c][d]);
if(c)dp[a][b][c][d]=max(dp[a][b][c][d],dp[a][b][c-1][d]);
if(d)dp[a][b][c][d]=max(dp[a][b][c][d],dp[a][b][c][d-1]);
从 1 1 1 到 n n n 枚举 a a a, b b b, c c c, d d d 的值就可以了。
#include<bits/stdc++.h>
using namespace std;
const int N=355;
const int M=125;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
int n,m;
int s[N];
int f[5];
int dp[M][M][M][M];
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++)s[i]=read();
for(int i=1;i<=m;i++)f[read()]++;//记录每种卡片张数
dp[0][0][0][0]=s[1];
for(int a=0;a<=f[1];a++){
for(int b=0;b<=f[2];b++){
for(int c=0;c<=f[3];c++){
for(int d=0;d<=f[4];d++){
if(!a&&!b&&!c&&!d)continue;
if(a)dp[a][b][c][d]=max(dp[a][b][c][d],dp[a-1][b][c][d]);
if(b)dp[a][b][c][d]=max(dp[a][b][c][d],dp[a][b-1][c][d]);
if(c)dp[a][b][c][d]=max(dp[a][b][c][d],dp[a][b][c-1][d]);
if(d)dp[a][b][c][d]=max(dp[a][b][c][d],dp[a][b][c][d-1]);
dp[a][b][c][d]+=s[a*1+b*2+c*3+d*4+1];
}
}
}
}
print(dp[f[1]][f[2]][f[3]][f[4]]);
}
背包问题
解决在代价能接受的情况下希望价值最高这类的问题。
例题1
题目描述
有一个最多能装
m
m
m 千克的背包,有
n
n
n 种物品,它们的重量分别是
W
1
W_1
W1,
W
2
…
W
n
W_2\ldots W_n
W2…Wn,它们的价值分别是
C
1
,
C
2
…
C
n
C_1,C_2\ldots C_n
C1,C2…Cn。
若每种物品只有一件,问能装入的最大总价值。
输入格式
第一行为两个整数
m
m
m 和
n
n
n,以下
n
n
n 行中,每行两个整数
W
i
W_i
Wi 和
C
i
C_i
Ci,分别代表第
i
i
i 件物品的重量和价值。
输出格式
输出一个整数,即最大价值。
我们可以用 d p i dp_i dpi 表示背包容量为 i i i 时可以获得的最大价值。
由于只有这么几种物品,所以 d p i dp_i dpi 只能由 d p i − w j ( 1 ≤ j ≤ n ) dp_{i-w_j(1\le j\le n)} dpi−wj(1≤j≤n) 转移而来。如果是第 j j j 个物品转移来的,那么有 d p i = d p i − w j + c j dp_i=dp_{i-w_j}+c_j dpi=dpi−wj+cj。
接下来该处理只能用一次物品的问题了,可以考虑把 i i i 从后向前枚举,在处理较大的背包空间时较小的还未被处理,所以不会把同一个物品使用两次。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();};
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
int n,m,k;
int T;
int w[N],c[N];
int dp[N];
signed main(){
m=read(),n=read();
for(int i=1;i<=n;i++)w[i]=read(),c[i]=read();
for(int i=1;i<=n;i++){
for(int j=m;j>=w[i];j--){
dp[j]=max(dp[j],c[i]+dp[j-w[i]]);
}
}
print(dp[m]);
}
例题2
题目描述
有一个背包容量为
v
v
v,同时有
n
n
n 个物品,每个物品有一个体积。要求从
n
n
n 个物品中,任取若干个装入包内,使背包的剩余空间为最小。
输入格式
第一行为一个整数,表示背包容量,第二行为一个整数,表示有
n
n
n 个物品,接下来
n
n
n 行,分别表示这
n
n
n 个物品的各自体积。
输出格式
只有一个整数,表示背包剩余空间。
使背包剩余空间最小,也就是说使用的空间最大,上一题的问题是空间合理时价值最大,这道题的价值变成了空间,其实是一个道理。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
int n,m;
int v[N];
int dp[N];
signed main(){
m=read(),n=read();
for(int i=1;i<=n;i++)v[i]=read();
for(int i=1;i<=n;i++){
for(int j=m;j>=v[i];j--){
//空间合理的情况下空间最大
dp[j]=max(dp[j],dp[j-v[i]]+v[i]);
}
}
print(m-dp[m]);
}
例题3
题目描述
有一个最多能装
m
m
m 公斤的背包,现在有
n
n
n 种物品,每种的重量分别是
W
1
W_1
W1,
W
2
…
W
n
W_2\ldots W_n
W2…Wn,每种的价值分别为
C
1
C1
C1,
C
2
…
C
n
C2\ldots Cn
C2…Cn。若每种物品的个数足够多,求能获得的最大总价值。
输入格式
第一行为两个整数,即
m
m
m,
n
n
n。
以后每行为两个整数,表示每个物品的重量和价值。
输出格式
获得的最大总价值。
上面例题 1 中已经提到过 DP 的思路,当时为了解决只能用一次的问题,特意将背包容量倒着枚举,而这一次无所顾忌了,每种物品有无穷多,所以正着枚举,这样使计算大容量时小容量已经考虑好。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();};
while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
int n,m,k;
int T;
int w[N],c[N];
int dp[N];
signed main(){
m=read(),n=read();
for(int i=1;i<=n;i++)w[i]=read(),c[i]=read();
for(int i=1;i<=n;i++){
for(int j=w[i];j<=m;j++){
//正序枚举,使更小的容量作为基石先考虑到
dp[j]=max(dp[j],c[i]+dp[j-w[i]]);
}
}
print(dp[m]);
}
例题4
题目描述
有
N
N
N 种物品和一个容量是
V
V
V 的背包。
第
i
i
i 种物品最多有
s
i
s_i
si 件,每件体积是
v
i
v_i
vi,价值是
w
i
w_i
wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。
输出最大价值。
输入格式
第一行两个整数
N
N
N,
V
V
V,用空格隔开,分别表示物品种数和背包容积。
接下来有
N
N
N 行,每行三个整数
v
i
,
w
i
,
s
i
v_i,w_i,s_i
vi,wi,si,用空格隔开,分别表示第
i
i
i 种物品的体积、价值和数量。
输出格式
输出一个整数,表示最大价值。
背包问题的思路已经在例题 1 说明了,现在的问题是数量的限制。
可以从 1 1 1 到 s i s_i si 枚举当前物品的数量,因为不是完全背包,所以还是得考虑数量的问题,同 0/1背包一样,也是倒序枚举背包容量,其实其它的就没什么不同了。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
int n,m;
int a[N];
int dp[N];
int v[N],w[N],s[N];
signed main(){
n=read(),m=read();
for(int i=1;i<=n;i++)v[i]=read(),w[i]=read(),s[i]=read();
for(int i=1;i<=n;i++){
for(int j=m;j>=v[i];j--){
for(int k=1;k<=s[i]&&k*v[i]<=j;k++){
dp[j]=max(dp[j],dp[j-k*v[i]]+k*w[i]);
}
}
}
print(dp[m]);
}
例题5
题目描述
有
N
N
N 件物品和一个容量是
V
V
V 的背包,背包能承受的最大重量是
M
M
M。
每件物品只能用一次。体积是
v
i
v_i
vi,重量是
m
i
m_i
mi,价值是
w
i
w_i
wi。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,总重量不超过背包可承受的最大重量,且价值总和最大。 输出最大价值。
输入格式
第一行三个整数,,用空格隔开,分别表示物品件数、背包容积和背包可承受的最大重量。
接下来有 行,每行三个整数 ,用空格隔开,分别表示第 件物品的体积、重量和价值。
输出格式
输出一个整数,表示最大价值。
开二维的数组 d p dp dp, d p i j dp_{ij} dpij 表示背包容量为 i i i,最大承重为 j j j 时的最大价值,其余与 0/1背包相同。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
const int M=1e3+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
int n,m,k;
int v[N],w[N],c[N];
int dp[M][M];
signed main(){
n=read(),m=read(),k=read();
for(int i=1;i<=n;i++)v[i]=read(),w[i]=read(),c[i]=read();
for(int i=1;i<=n;i++){
for(int j=m;j>=v[i];j--){
for(int l=k;l>=w[i];l--){
dp[j][l]=max(dp[j][l],dp[j-v[i]][l-w[i]]+c[i]);
}
}
}
print(dp[m][k]);
}
区间DP
处理一个连续序列中由区间两端扩展得到的全局最优。
区间 DP 通常是设
d
p
i
j
dp_{ij}
dpij 为序列从第
i
i
i 位到第
j
j
j 位这个区间的最优解,既然是区间,那就会有长度,会有起点。
区间 DP 就有一个模板:
for(int len=1;len<=n;len++){//长度
for(int i=1;i<=n-len+1;i++){//起点
int j=i+len-1;//终点
for(int k=i;j<j;k++){//枚举区间元素
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+价值);
//dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+代价);
}
}
}
例题1
P1775 石子合并(弱化版)
最后是求整个区间的和,并且选择之间互相影响,那么不能贪心,是 DP,区间 DP。
用
d
p
i
j
dp_{ij}
dpij 表示合并第
i
i
i 堆石头到第
j
j
j 堆石子用的最小代价。那么
d
p
i
j
dp_{ij}
dpij 可以由
d
p
i
k
+
d
p
(
k
+
1
)
j
+
∑
x
=
i
j
m
x
dp_{ik}+dp_{(k\!+\!1)j}+\sum_{x=i}^{j}m_x
dpik+dp(k+1)j+∑x=ijmx 转移而来,我们在
i
i
i 到
j
j
j 之间枚举
k
k
k 的值,求出
d
p
i
j
dp_{ij}
dpij 的最优。
从小到大枚举区间的长度,保证求一个区间时他包含的所有区间(除了他自己)一定被计算过。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
const int M=1e3+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
int n;
int a[N];
int s[N];
int dp[M][M];
signed main(){
n=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++)s[i]=s[i-1]+a[i];
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++)dp[i][i]=0;
for(int len=1;len<=n;len++){
for(int i=1;i<=n-len+1;i++){
int j=i+len-1;
for(int k=i;k<j;k++)dp[i][j]=min(dp[i][k]+dp[k+1][j]+s[j]-s[i-1],dp[i][j]);
}
}
print(dp[1][n]);//最后要求合并所有石子
}
例题2
P1880 [NOI1995] 石子合并
由于是环形的,所以可以把数组复制一遍到后面,其余相同。
注意有一个最小值还有最大值,要开两个数组分别算,注意初始化问题。
至于长度,只用枚举
1
1
1 到
n
n
n 就不会出现重复的问题了。
#include<bits/stdc++.h>
#define endl putchar('\n')
using namespace std;
const int N=1e6+5;
const int M=1e3+5;
int read(){
int x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x*f;
}
void print(int x){
if(x<0)putchar('-'),x=-x;
if(x<10){putchar(x+'0');return;}
print(x/10);
putchar(x%10+'0');
}
int n;
int a[N];
int sum[N];
int dp[M][M];
int pd[N][N];
signed main(){
n=read();
for(int i=1;i<=n;i++){
a[i]=read();
a[i+n]=a[i];
}
for(int i=1;i<=2*n;i++)sum[i]=sum[i-1]+a[i];
memset(dp,0x3f,sizeof(dp));
for(int len=1;len<=n;len++){
for(int i=1;i+len-1<=n*2;i++){
int j=i+len-1;
if(i==j)pd[i][j]=dp[i][j]=0;
else{
for(int k=i;k<j;k++){
pd[i][j]=max(pd[i][j],pd[i][k]+pd[k+1][j]+sum[j]-sum[i-1]);
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+sum[j]-sum[i-1]);
}
}
}
}
int mx=0;
int mn=1e9;
for(int i=1;i<=n;i++){
mx=max(mx,pd[i][i+n-1]);
mn=min(mn,dp[i][i+n-1]);
}
print(mn);
endl;
print(mx);
}