背包DP
01背包
原题链接
思路
用前i个物品,体积为j时的最大值
代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int v[N], w[N], f[N];
int main()
{
int n, V;
cin >> n >> V;
for (int i = 1; i <= n; i ++ )
{
cin >> v[i] >> w[i];
for (int j = V; j >= v[i]; j -- )
{
f[j] = max(f[j - v[i]] + w[i], f[j]);
}
}
cout << f[V] <<endl;
return 0;
}
完全背包
原题链接
思路
用前i个物品,体积为j时,的最大价值,j从小到大枚举,因为是在同一层,看体积为j时,用物品i的最大价值。
代码
//********简化版
// #include <iostream>
// #include <cstring>
// #include <algorithm>
// using namespace std;
// const int N = 1010;
// int v[N], w[N], f[N];
// int main()
// {
// int n, V;
// cin >> n >> V;
// for (int i = 1; i <= n; i ++ )
// {
// cin >> v[i] >> w[i];
// for (int j = v[i]; j <= V; j ++)//for (int j = 0; j <= V; j ++)
// { //f[j] = f[j];
// //if(j >= v[i)
// f[j] = max(f[j - v[i]] + w[i], f[j]);
// }
// }
// cout << f[V] <<endl;
// return 0;
// }
//***************朴素版
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010;
int n, V, v[N], w[N], f[N][N];
int main()
{
cin >> n >> V;
for(int i = 1; i <= n; i ++)
{
cin >> v[i] >> w[i];
for(int j = 0; j <= V; j ++)
{
f[i][j] = f[i - 1][j];//之前放不进去的时候就不放,就是i - 1 时的价值
if(j >= v[i])//有可能不存在所以需要判断
{
f[i][j] = max(f[i][j], f[i][j-v[i]] + w[i]);//一一枚举物品,能往里加就往里加,同一个物品,i不减1意思是在同一层上
}
}
}
cout << f[n][V] << endl;
return 0;
}
多重背包1
原题链接
思路
前i个物品,体积为j,用k个物品i时的最大价值
代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int v, w, s, f[N];//只能开一维,二维会错
int main()
{
int n, w, V;
cin >> n >> V;
for (int i = 1; i <= n; i ++ )//枚举前i个
{
cin >> v >> w >> s;
for (int j = V; j >= v; j -- )//01背包的扩展,所以从大到小
{
for (int k = 0; k <= s && v*k <= j; k ++ )//需要加一个v * s <= j,防止背包爆掉
{
f[j] = max(f[j], f[j - v * k] + w * k);
}
}
}
cout << f[V] << endl;
return 0;
}
多重背包2(二进制优化版)
原题链接
思路
每个物品有自己的个数,那么就用2的1, 1 * 2, 1 * 2 * 2…为个数打包物品,也就是说每组有2的n次方个物品,剩下的物品也为一组,然后就变成了每种物品的个数有n组,然后就枚举每种物品以每组物品为种数,然后枚举体积,做01背包操作即可。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 25000;
int n, V, v[N], cnt, w[N], k, f[N];
int main()
{
cin >> n >> V;
for(int i = 1; i <=n ; i ++)
{
int a, b, c;
cin >> a >> b >> c;//体积,价值,数量
int k = 1;//从一开始
while(k <= c)//一共只有c个
{
cnt ++;
v[cnt] = k * a;//k个为一组
w[cnt] = k * b;//k个为一组
c -= k; //-k
k *= 2;//二进制优化,所以乘二
}
if(c > 0)//如果没有用完,剩余的也打包放进数组
{
cnt ++;
v[cnt] = c * a;
w[cnt] = c * b;
}
}
n = cnt;//一共有cnt个包
//开始01背包的操作
for(int i = 1; i <= n; i ++)
{
for(int j = V; j >= v[i]; j --)
{
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
cout << f[V] << endl;
return 0;
}
二位费用背包
原题链接
思路
两个限制条件就变成二维数组,三种循环,前i个物品,体积不超过V,重量不超过M,的所有选法,取最大值。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, V, f[N][N], v, w, m, M;
int main()
{
cin >> n >> V >> M;
for(int i = 1; i <= n; i ++)
{
cin >> v >> m >> w;
for(int j = V; j >= v; j --)//体积
{
for(int k = M; k >= m; k --)//重量
{
f[j][k] = max(f[j][k], f[j- v][k - m] + w);
}
}
}
cout << f[V][M] << endl;
return 0;
}
分组背包
思路
先输入,用二维数组存体积和价值 例: v [ 组别 ] [ 第几个] = 体积;
然后就用01背包的方法,三重循环,前i个物品,体积为j时,用第k个 的最大体积。
代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 110;
int f[N], v[N][N], w[N][N], s[N], maxn = 0;
int n, V;
int main()
{
cin >> n >> V;
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 ++)
{
for(int j = V; j >= 0; j --)
{
for(int k = 0; k <= s[i]; k ++)
{
if(j >= v[i][k])
{
f[j] = max(f[j], f[ j - v[i][k] ] + w[i][k] );
}
}
}
}
cout << f[V] << endl;
return 0;
}
线性DP
数字三角形
https://www.acwing.com/problem/content/900/
思路
每一层每个数和它上面的左右两个数取最大值,维护每一层每个点的最大值,最后一层找最大值就是最大路径。
代码
#include <iostream>
#include <cstring>
#include <algorithm>
#define int long long
using namespace std;
const int N = 1005;
signed main()
{
int n, num[N][N]={0}, f[N][N] = {-0x3f3f3f};
cin >> n;
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= i; j ++ )
{
cin >> num[i][j];
}
}
for (int i = 0; i <= n; i ++ )
{
for (int j = 0; j <= n; j ++ )
{
f[i][j] = -0x3f3f3f;
}
}
f[0][1]=0;
for (int i = 1; i <= n; i ++ )
{
for (int j = 1; j <= i; j ++ )
{
f[i][j] = max(f[i - 1][j] + num[i][j], f[i - 1][j - 1] + num[i][j]);
}
}
int maxn = -0x3f3f3f3f;
for (int i = 1; i <= n; i ++ )
{
maxn = max(f[n][i], maxn);
}
cout << maxn <<endl;
return 0;
}
最长上升子序列
https://www.acwing.com/problem/content/897/
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int n;
int a[N], f[N];
int main()
{
cin >> n;
for(int i = 1; i <= n; i ++ )
{
cin >> a[i];
}
for(int i = 1; i <= n; i ++ )
{
f[i] = 1;//自己本身算一个
for(int j = 1; j < i; j ++ )
{
if(a[j] < a[i])//如果f[j]后面可以放f[i] ;
{
f[i] = max(f[j] + 1, f[i]);//是它本身现有的子序列大(有可能已经被赋过值)还是 当前的以f[j]结尾的子序列
}
}
}
int ans = -0x3f3f3f3f;
for(int i = 1; i <= n; i ++ )
{
ans = max(f[i], ans);
}
cout << ans;
return 0;
}
求最长子序列是什么(把子序列输出来)
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int n;
int a[N], f[N], g[N];
int main()
{
cin >> n;
for(int i = 1; i <= n; i ++ )
{
cin >> a[i];
}
for(int i = 1; i <= n; i ++ )
{
f[i] = 1;//自己本身算一个
for(int j = 1; j < i; j ++ )
{
if(a[j] < a[i])//如果f[j]后面可以放f[i] ;
{
//f[i] = max(f[j] + 1, f[i]);//是它本身现有的子序列大(有可能已经被赋过值)还是 当前的以f[j]结尾的子序列
if(f[i] < f[j] + 1)
{
f[i] = f[j] + 1;
g[i] = j;//记录是从哪个状态转移来的 ,记下来的一定是最大的,因为到最大的,其他的就不会更新了
//以上一层值为下标,记录下一层的下标
}
}
}
}
int ans = -0x3f3f3f3f, k = 1;
for(int i = 1; i <= n; i ++ )
{
if(f[k] < f[i])
{
k = i;
}
}
cout << f[k];
for(int i = 0 ; i < f[k]; i ++ )
{
cout << a[k] << " ";
k = g[k];//一层一层向上找该子序列的上一个数
}
return 0;
}
最长公共子序列
https://www.acwing.com/problem/content/899/
思路
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1005;
int n, m;
char a[N], b[N];
int f[N][N];
int main()
{
cin >> 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 - 1][j - 1] + 1, f[i][j]);
}
}
}
cout << f[n][m] << endl;
return 0;
}
最短编辑距离
https://www.acwing.com/problem/content/904/
思路
这题其实还不是那么明白
//就是把三种情况都枚举一遍 ,然后每一步都取最小值记录一下,维护每一步都是最小值,枚举每个子串的情况,一步一步累加到最后一步
代码
#include<bits/stdc++.h>
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);
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);
}
//cout <<"i == "<< i << " j == "<<j<<" "<< f[i][j] <<endl;
}
}
cout << f[n][m] << endl;
return 0;
}
编辑距离
https://www.acwing.com/problem/content/description/901/
思路
同上 最短编辑距离
代码
#include<bits/stdc++.h>
using namespace std;
int n, m, f[1005][1005];
char a[1005][1005];
int solved(char a[], char b[])
{
int lena = strlen (a + 1);
int lenb = strlen (b + 1);
for(int i = 0; i <= lena; i ++ )
{
f[i][0] = i;
}
for(int i = 0; i <= lenb; i ++ )
{
f[0][i] = i;
}
for(int i = 1; i <= lena; i ++ )
{
for(int j = 1; j <= lenb; 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 - 1][j - 1], f[i][j]);
}
else
{
f[i][j] = min(f[i - 1][j - 1] + 1, f[i][j]);
}
}
}
return f[lena][lenb];
}
int main()
{
cin >> n >> m;
for(int i = 1; i <= n; i ++ )
{
scanf("%s", a[i] + 1);
}
while(m -- )
{
int flag = 0, ans = 0;
char b[1005];
scanf("%s%d", b + 1, &flag);
for(int i = 1; i <= n; i ++ )
{
if(solved(a[i], b) <= flag )
{
ans ++ ;
}
}
cout << ans <<endl;
}
return 0;
}
区间DP
石子合并
https://www.acwing.com/problem/content/284/
思路
以最后一步进行分类,最后一步合并哪两个石子进行分类,枚举每个区间枚举每两部分石子,然后求MIN还不太懂,过几天取再复习一遍。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 310;
int n;
int s[N];
int f[N][N];
int main()
{
cin >> n;
for(int i = 1; i <= n; i ++ )
{
cin >> s[i];
//s[i] = s[i - 1] + s[i];
}
for(int i = 1; i <= n; i ++ )
{
//cin >> s[i];
s[i] += s[i - 1];
}
//memset(f, 999999, sizeof f);
for(int len = 2; len <= n; len ++ )
{
for(int i = 1; i + len - 1 <= n; i ++ )
{
int l = i; int r = i + len - 1;
f[l][r] = 1e9;
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]);
}
}
}
cout << f[1][n] << endl;
return 0;
}