线性DP的整合

引入

一个这样的问题,给定 n n n 个整数(可能为负) a 1 … a n a_1\ldots a_n a1an ,从中选出若干个数使其总和最大。
这明显就是贪心,正数就选,其余不管。
那如果增加一个规则,不能选相邻的两个数,就不能再贪心了。
比如:
− 1 − 5 4 2147483647 − 2 -1\quad -5\quad 4\quad 2147483647\quad -2 15421474836472
正常贪心在遇到 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} dp1n1 来推出最后的 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) (3N5000)
接下来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(0k2,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 W2Wn,它们的价值分别是 C 1 , C 2 … C n C_1,C_2\ldots C_n C1C2Cn
若每种物品只有一件,问能装入的最大总价值。

输入格式
第一行为两个整数 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)} dpiwj(1jn) 转移而来。如果是第 j j j 个物品转移来的,那么有 d p i = d p i − w j + c j dp_i=dp_{i-w_j}+c_j dpi=dpiwj+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 W2Wn,每种的价值分别为 C 1 C1 C1 C 2 … C n C2\ldots Cn C2Cn。若每种物品的个数足够多,求能获得的最大总价值。
输入格式
第一行为两个整数,即 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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值