动态规划
[TOC]
背包问题
01背包
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]); } }
完全背包
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]); } }
多重背包1
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-k*v[i]]+w[i]*k); } } }
多重背包2
for(int i=1;i<=n;i++){ cin>>a>>b>>c; int k=1; while(k<c){ v[++cnt]=a*k; w[cnt]=b*k; c-=k; k<<=1; } if(c){ v[++cnt]=c*a; w[cnt]=c*b; } } for(int i=1;i<=cnt;i++){ for(int j=m;j>=v[i];j--){ f[j]=max(f[j],f[j-v[i]]+w[i]); } }
多重背包3
#include <iostream> #include <cstring> using namespace std; const int N = 1010, M = 20010; int n, m; int v[N], w[N], s[N]; int f[M], g[M]; int q[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) { memcpy(g, f, sizeof g); for (int r = 0; r < v[i]; ++ r) { int hh = 0, tt = -1; for (int j = r; j <= m; j += v[i]) { while (hh <= tt && j - q[hh] > s[i] * v[i]) hh ++ ; while (hh <= tt && g[q[tt]] + (j - q[tt]) / v[i] * w[i] <= g[j]) -- tt; q[ ++ tt] = j; f[j] = g[q[hh]] + (j - q[hh]) / v[i] * w[i]; } } } cout << f[m] << endl; return 0; }
分组背包
//同组内只能选一个物品 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]); } } }
有依赖的背包
限制性条件:选第i件物品,就必须选择第j件物品,保证不会循环引用。
一般我们称呼不依赖于别的物品的物品为主件,依赖于某主件的物品称为附件。
一般采用树形DP 的方式来求解
关于集合的划分,针对于附件的数量有两种方式:
附件组合过多,则划分体积,时间复杂度为 $O(NVV)$
附件组合不多,体积较大时,则对附件组合进行分组背包DP,时间复杂度为$O(NV2^k)$
//以下为划分体积的方式,对附件组合进行dp,只需状态压缩遍历每种情况即可 #include <iostream> #include <cstring> using namespace std; const int N = 110; int n, m, root; int h[N], e[N], ne[N], idx; int v[N], w[N]; int f[N][N]; void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ; } void dfs(int u) { //先枚举所有状态体积小于等于j-v[u]的所有子节点们能够获得的最大价值 for (int i = h[u]; ~i; i = ne[i]) { int son = e[i]; dfs(son); //从下往上算,先计算子节点的状态 for (int j = m - v[u]; j >= 0; -- j) //枚举所有要被更新的状态 { for (int k = 0; k <= j; ++ k) //枚举该子节点在体积j下能使用的所有可能体积数 { f[u][j] = max(f[u][j], f[u][j - k] + f[son][k]); } } } //最后选上第u件物品 for (int j = m; j >= v[u]; -- j) f[u][j] = f[u][j - v[u]] + w[u]; for (int j = 0; j < v[u]; ++ j) f[u][j] = 0; //清空没选上u的所有状态 } int main() { memset(h, -1, sizeof h); cin >> n >> m; for (int i = 1; i <= n; ++ i) { int p; cin >> v[i] >> w[i] >> p; if (p == -1) root = i; else add(p, i); } dfs(root); cout << f[root][m] << endl; return 0; }
最长上升子序列
int main(void) {
int n; cin >> n;
vector<int>arr(n);
for (int i = 0; i < n; ++i)cin >> arr[i];
vector<int>stk;//模拟堆栈
stk.push_back(arr[0]);
for (int i = 1; i < n; ++i) {
if (arr[i] > stk.back())//如果该元素大于栈顶元素,将该元素入栈
stk.push_back(arr[i]);
else//替换掉第一个大于或者等于这个数字的那个数
*lower_bound(stk.begin(), stk.end(), arr[i]) = arr[i];
}
cout << stk.size() << endl;
return 0;
}
最长公共子序列
int n,m;
cin>>n>>m;
string aa,bb,a="1",b="1";
cin>>aa>>bb;
a+=aa;
b+=bb;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
f[i][j]=max(f[i][j-1],f[i-1][j]);
if(a[i]==b[j])f[i][j]=max(f[i][j],f[i-1][j-1]+1);
}
}
cout<<f[n][m];
最长公共上升子序列
#include<bits/stdc++.h>
using namespace std;
const int N = 3010;
int a[N], b[N], f[N][N];
int main(){
int n;
cin>>n;
for (int i = 1; i <= n; i ++ ) cin>>a[i];
for (int i = 1; i <= n; i ++ ) cin>>b[i];
for (int i = 1; i <= n; i ++ )
{
int maxv = 1;
for(int j = 1; j <=n ; j ++ )
{
f[i][j] = f[i - 1][j];
if (a[i] == b[j]) f[i][j] = max(f[i][j],maxv);
if (b[j] < a[i]) maxv = max(f[i][j]+1, maxv);
}
}
int res = 0;
for (int i = 1;i <= n; i ++) res = max(res, f[n][i]);
cout<<res;
return 0;
}
单调队列优化
for (int i = 1; i <= n; i ++ )
{
while (hh <= tt && i - que[hh] > m) hh ++ ;
res = max(res, s[i] - s[que[hh]]);
while (hh <= tt && s[que[tt]] >= s[i]) tt -- ;
que[ ++ tt] = i;
}
树形DP之换根DP
//换根DP 一般分为三个步骤:
//1.指定任意一个根节点
//2.一次dfs遍历,统计出当前子树内的节点对当前节点的贡献
//3.一次dfs遍历,统计出当前节点的父节点对当前节点的贡献,然后合并统计答案
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10010, M = N << 1, INF = 0x3f3f3f3f;
int n;
int h[N], e[M], w[M], ne[M], idx;
int d1[N], d2[N], up[N];
int s1[N], s2[N];
void add(int a, int b, int c)
{
e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs1(int u, int father)
{
// d1[u] = d2[u] = -INF; //这题所有边权都是正的,可以不用初始化为负无穷
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == father) continue;
dfs1(j, u);
if (d1[j] + w[i] >= d1[u])
{
d2[u] = d1[u], s2[u] = s1[u];
d1[u] = d1[j] + w[i], s1[u] = j;
}
else if (d1[j] + w[i] > d2[u])
{
d2[u] = d1[j] + w[i], s2[u] = j;
}
}
// if (d1[u] == -INF) d1[u] = d2[u] = 0; //特判叶子结点
}
void dfs2(int u, int father)
{
for (int i = h[u]; ~i; i = ne[i])
{
int j = e[i];
if (j == father) continue;
if (s1[u] == j) up[j] = w[i] + max(up[u], d2[u]); //son_u = j,则用次大更新
else up[j] = w[i] + max(up[u], d1[u]); //son_u != j,则用最大更新
dfs2(j, u);
}
}
int main()
{
memset(h, -1, sizeof h);
scanf("%d", &n);
for (int i = 1; i < n; i ++ )
{
int a, b, c;
scanf("%d%d%d", &a, &b, &c);
add(a, b, c), add(b, a, c);
}
dfs1(1, -1);
dfs2(1, -1);
int res = INF;
for (int i = 1; i <= n; i ++ ) res = min(res, max(d1[i], up[i]));
printf("%d\n", res);
return 0;
}