01背包问题
特点: 每件物品最多用一次,可以不用
N件物品,容量为V的背包。每件物品只能用一次。
朴素做法:
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N], w[N];
int n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
f[i][j] = f[i - 1][j];
if (j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
cout << f[n][m] << endl;
}
优化后的代码:
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int f[N];
int v[N], w[N];
int n, m;
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ ) cin >> v[i] >> w[i];
for (int i = 1; i <= n; i++) {
for (int j = m; j >= v[i]; j--) {
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[m] << endl;
}
完全背包问题
每件物品有无限多个
朴素做法
//会超时
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N],w[N];
int n,m;
int main()
{
cin>>n>>m;
for(int i = 1;i<=n;i++) cin>>v[i]>>w[i];
for(int i = 1;i<=n;i++)
for(int j = 0; j <= m;j++)
for(int k=0;v[i]*k<=j;k++)
f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
cout<<f[n][m]<<endl;
return 0;
}
优化算法(对比0-1背包的朴素做法)
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N][N];
int v[N],w[N];
int n,m;
int main()
{
cin>>n>>m;
for(int i = 1;i<=n;i++) cin>>v[i]>>w[i];
for(int i = 1;i<=n;i++)
for(int j = 0; j <= m;j++)
{
f[i][j] = f[i-1][j];
if(j>=v[i]) f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);
}
cout<<f[n][m]<<endl;
return 0;
}
对比:
0-1背包朴素 f[i][j] = max(f[i-1][j], f[i - 1][j - v[i]] + w[i]);//上一层:从大到小遍历
完全背包双循环 f[i][j] = max(f[i-1][j], f[i][j-v[i]]+w[i]);//本层:从小到大遍历
继续优化成一维
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N];
int v[N],w[N];
int n,m;
int main()
{
cin>>n>>m;
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 <= m;j++)
{
f[j] = max(f[j],f[j-v[i]]+w[i]);
}
cout<<f[m]<<endl;
return 0;
}
思考:为什么0-1背包是从大到小遍历体积,而完全背包是从小到大遍历体积?
从m往前不会重复,从前往后可以重复多个物品
多重背包问题
每件物品有s[i]件
朴素版本
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int f[N][N];
int v[N],w[N],s[N];
int n,m;
int main()
{
cin>>n>>m;
for(int i = 1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
for(int i = 1;i<=n;i++)
for(int j =0 ;j<=m;j++)
for(int k=0;k<=s[i] && k*v[i]<=j;k++)
f[i][j] = max(f[i][j],f[i-1][j-v[i]*k]+w[i]*k);
cout<<f[n][m]<<endl;
return 0;
}
二进制优化为0-1背包求解
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 22010;
int f[N];
int v[N],w[N];
int n,m;
int main()
{
cin>>n>>m;
int a,b,s,cnt;
//打包
for(int i=1;i<=n;i++)
{
cin>>a>>b>>s;
int k=1;
while(k<=s)
{
cnt++;
v[cnt] = k*a;
w[cnt] = k*b;
s -= k;
k *= 2;
}
if(s<k)
{
cnt++;
v[cnt] = a*s;
w[cnt] = b*s;
}
}
n = cnt;//按包更新物品总数
//0-1背包问题
for(int i = 1;i<=n;i++)
for(int j=m;j>=v[i];j--)
f[j] = max(f[j],f[j-v[i]]+w[i]);
cout<<f[m]<<endl;
return 0;
}
分组背包问题
把物品分成N组,每组有若干个,每组中最多只能选择一个
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n, m;
int v[N][N], w[N][N], s[N];
int f[N];
int main()
{
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
{
cin >> s[i];
for (int j = 0; j < s[i]; j ++ )
cin >> v[i][j] >> w[i][j];
}
for (int i = 1; i <= n; i ++ )
for (int j = m; j >= 0; j -- )
for (int k = 0; k < s[i]; k ++ )
if (v[i][k] <= j)
f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
cout << f[m] << endl;
return 0;
}
线性dp
- 数字三角形
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 510;
int n;
int a[N][N],f[N][N];
int INF = 1e9;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
scanf("%d",&a[i][j]);
for(int i = 0;i<=n;i++) //每行多初始化一个
for(int j=0;j<=i+1;j++)
f[i][j] = -INF;
f[1][1] = a[1][1];
for(int i=2;i<=n;i++)
for(int j=1;j<=i;j++)
f[i][j] = max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]);
int res = -INF;
for(int i=1;i<=n;i++)
res = max(res,f[n][i]);
printf("%d",res);
return 0;
}
AcWing 895. 最长上升子序列
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n;
int a[N], f[N];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]);
for (int i = 1; i <= n; i ++ )
{
f[i] = 1; // 只有a[i]一个数
for (int j = 1; j < i; j ++ )
if (a[j] < a[i])
f[i] = max(f[i], f[j] + 1);
}
int res = 0;
for (int i = 1; i <= n; i ++ ) res = max(res, f[i]);
printf("%d\n", res);
return 0;
}
AcWing 896. 最长上升子序列 II(偏贪心)
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 100010;
int n;
int a[N];//存储输入
int q[N];//存储每个长度结尾的最小值
int main()
{
scanf("%d", &n);
for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
int len = 0;
for (int i = 0; i < n; 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);//接a[i]后长度加1
q[r + 1] = a[i];
}
printf("%d\n", len);
return 0;
}
AcWing 897. 最长公共子序列
00 包含在01和10中
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
char a[N], b[N];
int f[N][N];
int main()
{
scanf("%d%d", &n, &m);
scanf("%s%s", a + 1, b + 1);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
{
f[i][j] = max(f[i - 1][j], f[i][j - 1]);
if (a[i] == b[j]) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1);
}
printf("%d\n", f[n][m]);
return 0;
}
AcWing 902. 最短编辑距离
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, m;
char a[N], b[N];
int f[N][N];
int main()
{
scanf("%d%s", &n, a + 1);//下标从1开始 因为有i-1
scanf("%d%s", &m, b + 1);
for (int i = 0; i <= m; i ++ ) f[0][i] = i;
for (int i = 0; i <= n; i ++ ) f[i][0] = i;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
{
f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
if (a[i] == b[j]) f[i][j] = min(f[i][j], f[i - 1][j - 1]);
else f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1);
}
printf("%d\n", f[n][m]);
return 0;
}
AcWing 899. 编辑距离(应用上一题目)
#include <iostream>
#include <algorithm>
#include <string.h>
using namespace std;
const int N = 15, M = 1010;
int n, m;
int f[N][N];
char str[M][N];
int edit_distance(char a[], char b[])
{
int la = strlen(a + 1), lb = strlen(b + 1);
for (int i = 0; i <= lb; i ++ ) f[0][i] = i;
for (int i = 0; i <= la; i ++ ) f[i][0] = i;
for (int i = 1; i <= la; i ++ )
for (int j = 1; j <= lb; j ++ )
{
f[i][j] = min(f[i - 1][j] + 1, f[i][j - 1] + 1);
f[i][j] = min(f[i][j], f[i - 1][j - 1] + (a[i] != b[j]));
}
return f[la][lb];
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i ++ ) scanf("%s", str[i] + 1);
while (m -- )
{
char s[N];
int limit;
scanf("%s%d", s + 1, &limit);
int res = 0;
for (int i = 0; i < n; i ++ )
if (edit_distance(str[i], s) <= limit)
res ++ ;
printf("%d\n", res);
}
return 0;
}
区间dp
AcWing 282. 石子合并
用到前缀和
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AQlRGXxf-1658110437969)(…/…/AppData/Roaming/Typora/typora-user-images/image-20220708111309646.png)]
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 310;
int n;
int s[N];//前缀和
int f[N][N];
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++ ) scanf("%d", &s[i]);
for (int i = 1; i <= n; i ++ ) s[i] += s[i - 1];
for (int len = 2; len <= n; len ++ )//长度
for (int i = 1; i + len - 1 <= n; i ++ )//起点
{
int l = i, r = i + len - 1;
f[l][r] = 1e8;
for (int k = l; k < r; k ++ )
f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
}
printf("%d\n", f[1][n]);
return 0;
}
核心代码
for (int len = 2; len <= n; len ++ )//枚举长度
for (int i = 1; i + len - 1 <= n; i ++ )//枚举起点
{
int l = i, r = i + len - 1;
f[l][r] = 1e8;
for (int k = l; k < r; k ++ )
f[l][r] = min(f[l][r], f[l][k] + f[k + 1][r] + s[r] - s[l - 1]);
计数类dp
AcWing 900. 整数划分

看成背包体积为n ,物品体积1~n,物品无限多个,恰好装满背包的方案数
完全背包问题
完全背包解法
状态表示:
f[i][j]表示只从1~i中选,且总和等于j的方案数
状态转移方程:
f[i][j] = f[i - 1][j] + f[i][j - i];
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int n;
int f[N];
int main()
{
cin >> n;
f[0] = 1;
for (int i = 1; i <= n; i ++ )
for (int j = i; j <= n; j ++ )
f[j] = (f[j] + f[j - i]) % mod;
cout << f[n] << endl;
return 0;
}
其他解法
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jEWmAFzj-1658110437970)(…/…/AppData/Roaming/Typora/typora-user-images/image-20220716160953719.png)]
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010, mod = 1e9 + 7;
int n;
int f[N][N];
int main()
{
cin >> n;
f[1][1] = 1;
for (int i = 2; i <= n; i ++ )
for (int j = 1; j <= i; j ++ )
f[i][j] = (f[i - 1][j - 1] + f[i - j][j]) % mod;
int res = 0;
for (int i = 1; i <= n; i ++ ) res = (res + f[n][i]) % mod;
cout << res << endl;
return 0;
}
数位统计DP(翻车呜呜呜)
分情况讨论
AcWing 338. 计数问题
前缀和思想 转化


#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 10;
/*
001~abc-1, 999
abc
1. num[i] < x, 0
2. num[i] == x, 0~efg
3. num[i] > x, 0~999
*/
int get(vector<int> num, int l, int r)
{
int res = 0;
for (int i = l; i >= r; i -- ) res = res * 10 + num[i];
return res;
}
int power10(int x)
{
int res = 1;
while (x -- ) res *= 10;
return res;
}
int count(int n, int x)
{
if (!n) return 0;
vector<int> num;
while (n)
{
num.push_back(n % 10);
n /= 10;
}
n = num.size();
int res = 0;
for (int i = n - 1 - !x; i >= 0; i -- )
{
if (i < n - 1)
{
res += get(num, n - 1, i + 1) * power10(i);
if (!x) res -= power10(i);
}
if (num[i] == x) res += get(num, i - 1, 0) + 1;
else if (num[i] > x) res += power10(i);
}
return res;
}
int main()
{
int a, b;
while (cin >> a >> b , a)
{
if (a > b) swap(a, b);
for (int i = 0; i <= 9; i ++ )
cout << count(b, i) - count(a - 1, i) << ' ';
cout << endl;
}
return 0;
}
状态压缩DP
朴素算法
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 12, M = 1 << N;
int n, m;
long long f[N][M];
bool st[M];
int main()
{
while (cin >> n >> m, n || m)
{
for (int i = 0; i < 1 << n; i ++ )
{
int cnt = 0;
st[i] = true;
for (int j = 0; j < n; j ++ )
if (i >> j & 1)
{
if (cnt & 1) st[i] = false;
cnt = 0;
}
else cnt ++ ;
if (cnt & 1) st[i] = false;
}
memset(f, 0, sizeof f);
f[0][0] = 1;
for (int i = 1; i <= m; i ++ )
for (int j = 0; j < 1 << n; j ++ )
for (int k = 0; k < 1 << n; k ++ )
if ((j & k) == 0 && st[j | k])
f[i][j] += f[i - 1][k];
cout << f[m][0] << endl;
}
return 0;
}
t main()
{
while (cin >> n >> m, n || m)
{
for (int i = 0; i < 1 << n; i ++ )
{
int cnt = 0;
st[i] = true;
for (int j = 0; j < n; j ++ )
if (i >> j & 1)
{
if (cnt & 1) st[i] = false;
cnt = 0;
}
else cnt ++ ;
if (cnt & 1) st[i] = false;
}
memset(f, 0, sizeof f);
f[0][0] = 1;
for (int i = 1; i <= m; i ++ )
for (int j = 0; j < 1 << n; j ++ )
for (int k = 0; k < 1 << n; k ++ )
if ((j & k) == 0 && st[j | k])
f[i][j] += f[i - 1][k];
cout << f[m][0] << endl;
}
return 0;
}