动态规划【上】

目录

背包问题

01背包问题

完全背包问题

多重背包问题

多重背包问题Ⅱ

分组背包问题

线性DP

数字三角形

最长上升子序列

最长上升子序列Ⅱ

最长公共子序列

最短编辑距离

编辑距离

区间DP

石子合并Ⅰ

石子合并Ⅱ

石子合并Ⅲ


背包问题

01背包问题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量且总价值最大,输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i件物品的体积和价值。
输出格式
输出一个整数,表示最大价值.
数据范围

0<N,V≤1000
0<vi,wi≤1000

输入样例:

4 5
1 2
2 4
3 4
4 5

输出样例:

8

思路:二维dp数组01背包: dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少

有两个方向推出来dp[i][j],

  • 不放物品i:背包容量为j,里面不放物品i的最大价值dp[i][j]就是dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以背包内的价值依然和前面相同。)
  • 放物品i:dp[i - 1][j - v[i]] 为背包容量为j - v[i]的时候不放物品i的最大价值,那么dp[i - 1][j - v[i]] + w[i] (物品i的价值),就是背包放物品i得到的最大价值;

所以递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]);

优化:如果把dp[i - 1]那一层拷贝到dp[i]上,表达式完全可以是:dp[i][j] = max(dp[i][j], dp[i][j - v[i]] + w[i]);与其把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了,只用dp[j]。倒序遍历是为了保证物品i只被放入一次!

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int v[N], w[N], dp[N];
int main()
{
	int n, c;
	cin >> n >> c;
	for(int i = 1; i <= n; i++)cin >> v[i] >> w[i];
	for(int i = 1; i <= n; i++){
		for(int j = c; j >= v[i]; j--){
			dp[j] = max(dp[j], dp[j-v[i]]+ w[i]);
		}
	}
	cout << dp[c];
	return 0;
 } 

完全背包问题

有 N种物品和一个容量是 V 的背包,每种物品都有无限件可用第种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量且总价值最大。输出最大价值。
输入格式:
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i种物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围

0<N,V≤1000
0<vi,wi≤1000

输入样例:

4 5
1 2
2 4
3 4
4 5

输出样例:

10

思路: dp[i][j] 的含义是在前 i 种物品中进行选择,总体积不大于 j 所获得的最大价值

  • 不放物品i:dp[i][j] = dp[i-1][j]
  • 放物品i:物品有无限个,装入第 i 种商品后还能继续装入第 i 种, 即转移到 dp[i][j-w[i]]

所以递归公式: dp[i][j] = max(dp[i - 1][j], dp[i][j - v[i]] + w[i]);

优化:这里的max第二项是dp[i],而01背包是dp[i-1],这里就需要覆盖,而01背包要避免覆盖。所以  dp[j] = max(dp[j], dp[j-v[i]]+ w[i]);

#include <bits/stdc++.h>
using namespace std;
int v[1010], w[1010], dp[1010];
int main()
{
    int n, c;
    cin >> n >> c;
    for(int i = 1; i <= n; i++)cin >> v[i] >> w[i];
    for(int i = 1; i <= n; i++){
        for(int j = v[i]; j <= c; j++){
            dp[j] = max(dp[j], dp[j-v[i]]+ w[i]);
        }
    }
    cout << dp[c];
    return 0;
}

多重背包问题

有 N 种物品和一个容量是 V的背包第种物品最多有 si件,每件体积是 vi ,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大,输出最大价值。
输入格式:
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi, si,用空格隔开,分别表示第i 种物品的体积、价值和数量。
输出格式:
输出一个整数,表示最大价值。
数据范围:

0<N,V≤100
0<vi,wi,si≤100

输入样例:

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例:

10

思路:多重背包是01背包的升级,dp[i][j]表示从前i个物品选且总体积不超过 j 的所有方案的最大值。 第 i 个物品可以有 0,1,2.... s[i]个。故状态方程式为:

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, c;
int v[N], w[N], s[N];
int dp[N][N], f[N];
int main()
{
	cin >> n >> c;
	for(int i = 1; i <= n; i++)cin >> v[i] >> w[i] >> s[i];
	for(int i = 1; i <= n; i++){
		for(int j = c; j >= 0; j--){
			for(int k = 0; k <= s[i]; k++){
				if(k * v[i] <= j){
					f[j] = max(f[j], f[j - k * v[i]] + k * w[i]);
				}
			}
		}
	}
	cout << f[c];
// 	for(int i = 1; i <= n; i++){
// 		for(int j = 0; j <= c ; j++){
// 			for(int k = 0; k <= s[i]; k++){
// 				if(k * v[i] <= j)
// 				dp[i][j] = max(dp[i][j], dp[i - 1][j - k * v[i]] + k * w[i]);
// 			}
// 		}
// 	}
// 	cout << dp[n][c];
	return 0;
 } 

多重背包问题Ⅱ

有 N 种物品和一个容量是 V的背包第种物品最多有 si件,每件体积是 vi ,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大,输出最大价值。
输入格式:
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。
接下来有 N 行,每行三个整数 vi,wi, si,用空格隔开,分别表示第i 种物品的体积、价值和数量。
输出格式:
输出一个整数,表示最大价值。
数据范围:

0<N≤1000
0<V≤2000
0<vi,wi,si≤2000

提示:本题考查多重背包的二进制优化方法。

输入样例:

4 5
1 2 3
2 4 1
3 4 3
4 5 2

输出样例:

10

思路:二进制拆分,假设s[i],可以分为 1 2 4  8 16 .... 2 ^ n 个,每组对应的 v[i], w[i]均可倍增计算出来,剩余 s[i] - 2^n 另外计算;最后用01背包的方式递推可得出结果。

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, m;
int v[N], w[N], dp[N], cnt;

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++){
        int a, b, c;//输入
        cin >> a >> b >> c;
        int k = 1;//分组
        while(k <= c){
            cnt ++;
            v[cnt] = a * k;
            w[cnt] = b * k;
            c -= k;
            // k <<= 1;
            k*=2;
        }
        if(c > 0){//剩余
            cnt++;
            v[cnt] = a * c;
            w[cnt] = b * c;
        }
    }
    for(int i = 1; i <= cnt; i++){//01背包
        for(int j = m; j >= v[i]; j--){
            dp[j] = max(dp[j], dp[j-v[i]]+w[i]);
        }
    }
    cout << dp[m];
    return 0;
}

分组背包问题

有 N 组物品和一个容量是 V的背包
每组物品有若干个,同一组内的物品最多只能选一个

每件物品的体积是 vij,价值是 wij,其中 i 是组号,j是组内编号。
求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行有两个整数 N,V,用空格隔开,分别表示物品组数和背包容量,
接下来有 N 组数据:
* 每组数据第一行有一个整数 Si,,表示第 i个物品组的物品数量

* 每组数据接下来有 Si行,每行有两个整数  vij, wij,用空格隔开,分别表示第 i 个物品组的第 j 个物品的体积和价值;
输出格式
输出一个整数,表示最大价值。
数据范围

0<N,V≤100
0<Si≤100
0<vij,wij≤100

输入样例:

3 5
2
1 2
2 4
1
3 4
1
4 5

输出样例:

8

思路:每组s个物品,有s+1种选法,枚举第i组,体积,以及第i组的第k个是否要选择。

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, c;
int v[N][N], w[N][N], s[N], dp[N];
int main()
{
    cin >> n >> c;
    for(int i = 1; i <= n; i++){
        cin >> s[i];
        for(int j = 1; j <= s[i]; j++)cin >> v[i][j] >> w[i][j];
    }
    for(int i = 1; i <= n; i++){//类似01背包
        for(int j = c; j >= 0; j--){
            for(int k = 1; k <= s[i]; k++){//枚举第i组的第k个物品
                if(v[i][k] <= j){//可选
                    dp[j] = max(dp[j], dp[j-v[i][k]]+w[i][k]);
                }
            }
        }
    }
    cout << dp[c];
    return 0;
}

线性DP

数字三角形

给定一个如下图所示的数字三角形,从顶部出发,在每一结点可以选择移动至其左下方的结点或移动至其右下方的结点,一直走到底层,要求找出一条路径,使路径上的数字的和最大。

输入格式:
第一行包含整数 n,表示数字三角形的层数。
接下来 n 行,每行包含若干整数,其中第 行表示数字三角形第  层包含的整数
输出格式:
输出一个整数,表示最大的路径数字和。
数据范围:

1≤n≤500,
−10000≤三角形中的整数≤10000

输入样例:

5
7
3 8
8 1 0 
2 7 4 4
4 5 2 6 5

输出样例:

30

思路:用二维数组dp[i][j]表示 走到第 i 行, 第 j 个数的最大路径和;该点可以由dp[i-1][j-1] 或者dp[i-1][j]走到,故 转移方程式为 dp[i][j] = max(dp[i-1][j-1], dp[i-1][j])+cur[j];

优化:递推到第i行时,只需要第 i-1行的数据,所以用两个一维数组,存一下当前行和上一行的数据即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 10100;
int n;
int a[N], cur[N], pre[N];
int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++){
        for(int j = 1; j <= i; j++){
            cin >> cur[j];//输入第 i 行
        }
        for(int j = 1; j <= i; j++){
            //走到[i][j] 是 由 [i-1][j-1] 与 [i-1][j]的最大值加上当前值
            if(j == 1)cur[j] += pre[j];//第1个数
            else if(j == i){//最后一个数
                cur[j] += pre[j-1];
            }
            else
                cur[j] += max(pre[j-1], pre[j]); 
        }
        for(int j = 1; j <= i; j++){
            pre[j] = cur[j];
        }
    }
    int mx = cur[1];
    for(int i = 2; i <= n; i++)mx = max(mx, cur[i]);
    cout << mx;
    return 0;
}

最长上升子序列

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

输入格式第一行包含整数 N
第二行包含 N 个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围:

1≤N≤1000,
−10^9≤数列中的数≤10^9

输入样例:

7
3 1 2 1 8 5 6

输出样例:

4

思路:dp[i] 表示为 以a[i]为结尾的最长单调递增子序列。一开始一个元素为符合条件,故初始状态为1,如果a[j] < a[i] 递增,就是 dp[i] 会是( 以a[ j ]为结尾的最长单调递增子序列, a[i]) 或是( 以a[i]为结尾的最长单调递增子序列),取最大值

最后,最长序列结尾元素是数组中某一个元素,遍历以每个元素为结尾的最长单调递增自序列,得到结果。

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
typedef long long ll;
int n;
ll a[N], dp[N];
//dp[i] 以a[i]为结尾的最长单调递增子序列

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)cin >> a[i], dp[i] = 1;//初始状态
    for(int i = 1; i <= n; i++){
        for(int j = 1; j < i; j++){
            if(a[j] < a[i]){//递增
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }
    }
    ll mx = dp[1];
    for(int i = 2; i <= n; i++)mx = max(mx, dp[i]);
    cout << mx;
    return 0;
}

最长上升子序列Ⅱ

给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。

输入格式第一行包含整数 N
第二行包含 N 个整数,表示完整序列。
输出格式
输出一个整数,表示最大长度。
数据范围:

1≤N≤1000,
−10^9≤数列中的数≤10^9

输入样例:

7
3 1 2 1 8 5 6

输出样例:

4

遍历a数组里面的数,然后在q这个数组里面查找是否存在一个大于且最靠近他的数
不存在【这个数是是前q个中最大的】
若存在,说明该点的最长子序列一定是前面其中的某个答案,只需要将里面地最靠近的那个元素进行更新即可

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int a[N], q[N];
int main()
{
	int n;
	cin >> n;
	for(int i = 1; i <= n; i++)cin >> a[i];
	int len = 0;
	for(int i = 1; i <= n; i++){
		//找到最大的且最靠近a[i]的数 
		int l = 0, r = len;
		while(l < r){
			int mid = l + r + 1 >> 1;
			if(q[mid] < a[i]) l = mid;
			else r = mid - 1; 
		}
		len = max(len, r + 1);//比较长度 
		q[r+1] = a[i];//存入 
	}
	cout << len;
	return 0;
}

最长公共子序列

给定两个长度分别为 N和M的字符串 A和B,求既是 A的子序列又是 B 的子序列的字符串长度最长是多少。
输入格式:
第一行包含两个整数 N 和M
第二行包含一个长度为 N的字符串,表示字符串 A第三行包含一个长度为 M 的字符串,表示字符串 B
字符串均由小写字母构成。
输出格式:
输出一个整数,表示最大长度。
数据范围

1≤N,M≤1000

输入样例:

输出样例:

思路:dp[i][j]表示A中前i-1个元素与B中前j-1个元素 的最长公共子序列;
当 a[i] == b[j]时,就是A前i-1个与B前j-1个元素 + 当前字符a[i] 即 dp[i][j] + 1
当 a[i] != b[j]时,就是A中新增的字符或者B中新增的字符对最长公共子序列结果不影响max(dp[i][j+1], dp[i+1][j]);

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int dp[N][N];
int main()
{
    int n, m;
    cin >> n >> m;
    string a, b;
    cin >> a >> b;
    for(int i = 0; i < a.size(); i++){
        for(int j = 0; j < b.size(); j++){
            if(a[i] == b[j]){
                dp[i+1][j+1] = dp[i][j] + 1;
            }else{
                dp[i+1][j+1] = max(dp[i][j+1], dp[i+1][j]);
            }
        }
    }
    cout << dp[a.size()][b.size()];
    return 0;
}

最短编辑距离

给定两个字符串 A和 B,现在要将 A 经过若操作变为 B,可进行的操作有:
1.删除-将字符串 A 中的某个字符删除
2.插入-在字符串A的某个位置插入某个字符
3.替换-将字符串 A 中的某个字符替换为另一个字符
现在请你求出,将 A变为 B至少需要进行多少次操作
输入格式
第一行包含整数 n,表示字符串 A 的长度
第二行包含一个长度为 n的字符串 A
第三行包含整数 m,表示字符串 B的长度.

第四行包含一个长度为 m 的字符串 B

字符串中均只包含大小写字母
输出格式
输出一个整数,表示最少操作次数。
数据范围

1≤n,m≤1000

输入样例:

10 
AGTCTGACGC
11 
AGTAAGTAGGC

输出样例:

4

删除操作:删掉a[i], a[0~i-1]与b[0~j]匹配, dp[i][j + 1] + 1;

插入操作:插入后 a[i] , a[0~i]与b[0~j-1]匹配,则 dp[i+1][j] + 1;

替换操作:a[i] 替换成b[j], a[0~i-1]与b[0~j-1]匹配,则dp[i][j]+1;
                如果 a[i] == b[j-1],那么不用改,dp[i][j]+0;

当A串为0时,删除操作 f[0][i] = i; 当B串为0时,删除操作 f[i][0] = i;

#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
int dp[N][N];
//dp[i][j] 表示 A[0:i-1]变为 B[0:j-1]的最少编辑次数 
int main()
{
	int n, m;
	string a, b;
	cin >> n >> a >> m >> b;
	for(int i = 0; i <= n; i++)dp[i][0] = i;//B串为0时
	for(int i = 0; i <= m; i++)dp[0][i] = i;//A串为0时
	for(int i = 0; i < n; i++){
		for(int j = 0; j < m; j++){
			// A[0:i]与 B[0:j]的最长 =( 删掉后A[0:i]与 B[0:j-1] 或 增加后A[0:i-1]与 B[0:j] ) 
			dp[i+1][j+1] = min(dp[i][j+1], dp[i+1][j])+1; 
			if(a[i] == b[j]){//两个字符相同,只可能删除或增加 
				dp[i+1][j+1] = min(dp[i+1][j+1], dp[i][j]);
			}else{
				dp[i+1][j+1] = min(dp[i+1][j+1], dp[i][j]+1);
			} 
		}
	}
	cout << dp[n][m];
	return 0;
}

编辑距离

给定 n 个长度不超过 10 的字符串以及 m 次询问,每次询问给出一个字符串和一个操作次数上限。
对于每次询问,请你求出给定的 n个字符串中有多少个字符串可以在上限操作次数内经过操作变成询问给出的字符串。
每个对字符串进行的单个字符的插入、删除或替换算作一次操作。
输入格式:
第一行包含两个整数n和m。
接下来 n 行,每行包含一个字符串,表示给定的字符串。
再接下来 m 行,每行包含一个字符串和一个整数,表示一次询问。
字符串中只包含小写字母,且长度均不超过 10。
输出格式:
输出共 m行,每行输出一个整数作为结果,表示一次询问中满足条件的字符串个数。
数据范围:

1≤n,m≤1000,

输入样例:

3 2
abc
acd
bcd
ab 1
acbd 2

输出样例:

1
3

思路:遍历n个字符串,判断(字符串变成询问字符串 s 的最短编辑距离)是否在上限次数内,是则+1。

#include <bits/stdc++.h>
using namespace std;
int n, m;
string str[1010];
int dp[1010][1010];

//最短编辑距离 
int change(string a, string b){
	for(int i = 0; i <= a.size(); i++) dp[i][0] = i;
	for(int i = 0; i <= b.size(); i++) dp[0][i] = i;
	for(int i = 0; i < a.size(); i++){
		for(int j = 0; j < b.size(); j++){
			dp[i+1][j+1] = min(dp[i][j+1], dp[i+1][j])+1;//删除或者增加
			if(a[i] == b[j]){
				dp[i+1][j+1] = min(dp[i+1][j+1], dp[i][j]);
			} else{
				dp[i+1][j+1] = min(dp[i+1][j+1], dp[i][j]+1);
			}
		}
	}
	return dp[a.size()][b.size()];
} 

int main()
{
	cin >> n >> m;
	for(int i = 0; i < n; i++)cin >> str[i];
	while(m--){
		string s;
		int k, res = 0;
		cin >> s >> k;
		for(int i = 0; i < n; i++){
			if(change(str[i], s) <= k)res++;//在上限操作次数内能变成 s 
		}
		cout << res << '\n';
	}
	return 0;
}

区间DP

石子合并Ⅰ

设有 N 堆石子排成一排,其编号为 1,2,3,...,N.
每堆石子有一定的质量,可以用一个整数来描述,现在要将这 N 堆石子合并成为一堆。
每次只能合并相邻的两堆,合并的代价为这两堆石子的质量之和,合并后与这两堆石子相邻的石子将和新堆相邻,合并时由于选择的顺序不同,合并的总代价也不相同。
例如有 4 堆石子分别为 1 3 5 2,我们可以先合并 1、2 堆,代价为4,得到 4 5 2,又合并1、2堆,代价为 9,得到 9 2 ,再合并得到 11,总代价为 4 +9 + 11 = 24
如果第二步是先合并 2、3 堆,则代价为 7,得到 4 7,最后一次合并代价为 11,总代价为 4 +7 +11 =22
问题是: 找出一种合理的方法,使总的代价最小,输出最小代价。
输入格式:
第一行一个 N 表示石子的堆数 N
第二行 N个数,表示每堆石子的质量(均不超过 1000)。
输出格式:
输出一个整数,表示最小代价。
数据范围:

1≤N≤300

输入样例:

4
1 3 5 2

输出样例:

22

思路:1到第n堆两两合并,可以是1和(合并的2~n),也可以是(合并的1~2)和(合并的3~n),如此迭代下去,1~n可以是(每个分割点2~n-1)合并后得分的最小值

状态转移方程为 dp[i][end] = min(dp[i][end], dp[i][j]+dp[j+1][end]+s[end]-s[i-1]);

#include <bits/stdc++.h>
using namespace std;
const int N = 310;
int n;
int a[N], s[N], dp[N][N];

int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)cin >> a[i], s[i] = s[i-1] + a[i];//前缀和
    for(int len = 2; len <= n; len++){//长度
        for(int i = 1; i + len - 1 <= n; i++){//起点
            int end = i + len - 1;//终点
            dp[i][end] = 2e9;
            for(int j = i; j < end; j++){//遍历分割点
                dp[i][end] = min(dp[i][end], dp[i][j]+dp[j+1][end]+s[end]-s[i-1]);
            }
        }
    }
    cout << dp[1][n];//1-n的最小值
    return 0;
}

石子合并Ⅱ

将 n 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分请编写一个程序,读入堆数 n 及每堆的石子数,并进行如下计算!
选择一种合并石子的方案,使得做 n -1次合并得分总和最大
选择一种合并石子的方案,使得做 n -1次合并得分总和最小。
输入格式
第一行包含整数n,表示共有 n 堆石子
第二行包含 n 个整数,分别表示每堆石子的数量。
输出格式
输出共两行:
第一行为合并得分总和最小值
第二行为合并得分总和最大值。
数据范围:

思路:环形,与线性同个原理,可以把区间增加为2n(一环就是两个n),然后再用线性的思路计算。

#include <bits/stdc++.h>
using namespace std;
const int N = 410;
int n;
int a[N], s[N];
int fa[N][N], fi[N][N];
int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)cin >> a[i], a[i+n] = a[i];
    for(int i = 1; i <= 2 * n; i++)s[i] = s[i-1] + a[i];//前缀和
    for(int len = 2; len <= n; len++){//长度
        for(int i = 1; i + len - 1 <= 2 * n; i++){//起始点
            int end = i + len -1;//终止点
            fi[i][end] = 2e9;
            fa[i][end] = -1;
            for(int j = i; j < end; j++){//遍历每两个合并的最大/小值
                fi[i][end] = min(fi[i][end], fi[i][j] + fi[j+1][end] + s[end] - s[i-1]);
                fa[i][end] = max(fa[i][end], fa[i][j] + fa[j+1][end] + s[end] - s[i-1]);
            }
        }
    }
    int ma = fa[1][n], mi = fi[1][n];
    for(int i = 2; i <= n; i++){
        ma = max(ma, fa[i][i + n - 1]);// [i, i+n-1]一环n个
        mi = min(mi, fi[i][i + n - 1]);
    }
    cout << mi << '\n';
    cout << ma << '\n';
    return 0;
}

思路2:dp[i][j]存储为 起点为 i, 长度为 j 的得分最值,分割点变为为[i : i + j - 1] [i + j : i + len - 1]

#include <bits/stdc++.h>
using namespace std;
const int N = 410;
int n;
int a[N], s[N];
int fa[N][N], fi[N][N];
int sum(int i, int len)//起点为i,长度为len的总长
{
    if(i + len-1 <= n){
        return s[i + len - 1] - s[i-1];
    }else{
        return s[n] - s[i-1] + s[(i + len - 1) % n];
    }
}
int main()
{
    cin >> n;
    for(int i = 1; i <= n; i++)cin >> a[i], s[i] = s[i-1] + a[i];
    for(int len = 2; len <= n; len ++){//长度
        for(int i = 1; i <= n; i++){//起点
            fi[i][len] = 2e9;
            fa[i][len] = 0;
            for(int j = 1; j < len; j++){//遍历长度
                //分割:[i : i + j - 1] [i + j : i + len - 1]
                fi[i][len] = min(fi[i][len], fi[i][j]+fi[(i+j-1)%n+1][len-j]+sum(i, len));
                fa[i][len] = max(fa[i][len], fa[i][j]+fa[(i+j-1)%n+1][len-j]+sum(i, len));
            }
        }
    }
    int ma = fa[1][n], mi = fi[1][n];
    for(int i = 2; i <= n; i++){
        ma = max(ma, fa[i][n]);
        mi = min(mi, fi[i][n]);
    }
    cout << mi << '\n';
    cout << ma << '\n';
    return 0;
}

本篇是关于动态规划的内容,写了最常见的 “背包”系列, 线性DP问题和区间DP问题。


“你们中大多数人都熟悉程序员的美德,有三种:那就是懒惰、急躁和傲慢。”——Larry Wall,Perl语言发明人

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值