洛谷 P1616 疯狂的采药
思路:
这题是一个标准的完全背包问题,在分析的时候我们就把题目中的背景去掉,体积就是时间,这样更有利于分析此题以及联想其他背包题目
不难得出递推式:dp[j] = max(dp[j - h[i]] + w[i], dp[j]);
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
typedef long long ll;
int h[N], w[N];
const int M = 1e7 + 10;
ll dp[M];
int main()
{
int t, m;
scanf("%d%d", &t, &m);
for (int i = 1; i <= m; i++)
{
scanf("%d%d", &h[i], &w[i]);
}
for (int i = 1; i <= m; i++)
{
for (int j = h[i]; j <= t; j++)
{
dp[j] = max(dp[j - h[i]] + w[i], dp[j]);
}
}
printf("%lld\n", dp[t]);
return 0;
}
洛谷 P1833 樱花
思路:这题是一道混合背包题,判断是完全,01,或是多重,再选择
状态转移方程:dp[j] = max(dp[j - t[i]] + w[i], dp[j]);
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
int t[N], w[N], p[N];
int dp[1008];
int main()
{
int h1, h2, m1, m2;
scanf("%d:%d %d:%d", &h1, &m1, &h2, &m2);
int n;
scanf("%d", &n);
int ans = (m2 - m1) + (h2 - h1) * 60;
for (int i = 1; i <= n; i++)
{
scanf("%d%d%d", &t[i], &w[i], &p[i]);
}
for (int i = 1; i <= n; i++)
{
if (p[i] == 0) //完全背包
{
for (int j = t[i]; j <= ans; j++)
{
dp[j] = max(dp[j - t[i]] + w[i], dp[j]);
}
}
else //多重背包和01背包
{
for (int k = 1; k <= p[i]; k++)
{
for (int j = ans; j >= k * t[i]; j--)
{
dp[j] = max(dp[j - t[i]] + w[i], dp[j]);
}
}
}
}
printf("%d\n", dp[ans]);
return 0;
}
洛谷 P1855 榨取kkksc03
思路:这题是一个经典的二维背包题,就是在01背包的基础上加上一层循环即可
状态转移方程:dp[j][k] = max(dp[j][k], dp[j - a[i]][k - b[i]] + 1);
#include <bits/stdc++.h>
using namespace std;
int a[108], b[108];
int dp[300][300];
int main()
{
int n, m, t;
scanf("%d%d%d", &n, &m, &t);
for (int i = 1; i <= n; i++)
{
scanf("%d%d", &a[i], &b[i]);
}
for (int i = 1; i <= n; i++)
{
for (int j = m; j >= a[i]; j--)
{
for (int k = t; k >= b[i]; k--)
{
dp[j][k] = max(dp[j][k], dp[j - a[i]][k - b[i]] + 1);
}
}
}
printf("%d\n", dp[m][t]);
return 0;
}
洛谷 P1757 通天之分组背包
思路:
正如题目名所说,这是一道分组背包的题
只要在01背包板子上改动一点:求背包的组数(直接在输入时求就行了),然后加层循环就行了
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
int cnt[N];
int dp[N];
int a[N], b[N];
int vis[N][N];
int main()
{
int m, n;
scanf("%d%d", &m, &n);
int ans;
int maxn = 0;
for (int i = 1; i <= n; i++)
{
scanf("%d%d%d", &a[i], &b[i], &ans);
++cnt[ans];
vis[ans][cnt[ans]] = i;
maxn = max(maxn, ans);
}
for (int i = 1; i <= maxn; i++)
{
for (int j = m; j >= 0; j--)
{
for (int k = 1; k <= cnt[i]; k++)
{
if (j >= a[vis[i][k]])
{
dp[j] = max(dp[j], dp[j - a[vis[i][k]]] + b[vis[i][k]]);
}
}
}
}
printf("%d\n", dp[m]);
return 0;
}
洛谷 P1064 [NOIP2006 提高组] 金明的预算方案
思路:带有附件的背包问题,它属于01背包的变式。这题还好,每一个物品最多只有两个附件,那么我们在对主件进行背包的时候,决策就不再是两个了,而是五个。
分别是:
1.不选,然后去考虑下一个
2.选且只选这个主件
3.选这个主件,并且选附件1
4.选这个主件,并且选附件2
5.选这个主件,并且选附件1和附件2.
我们令dpmainv数组表示某个主件的费用,而dpmainw数组表示某个主件的价值。
同样的,用二维数组dpannexv表示某个附件的费用,dpannexw表示某个附件的价值,第二维只需要0,1,2这三个数,其中第二维是0的场合表示这个主件i的附件数量,它只能等于0或1或2。第二维是1或者是2的值代表以i为主件的附件1或者附件2的相关信息(费用 价值)。
这样,状态转移方程就是四个。
不选附件的①:dp[j] = max(dp[j], dp[j - dpmainv[i]] + dpmainw[i]);
选附件1的②:dp[j] = max(dp[j], dp[j - dpmainv[i] - dpannexv[i][1]] + dpmainw[i] + dpannexw[i][1]);
选附件2的③:dp[j] = max(dp[j], dp[j - dpmainv[i] - dpannexv[i][2]] + dpmainw[i] + dpannexw[i][2]);
选附件1和附件2的④:dp[j] = max(dp[j], dp[j - dpmainv[i] - dpannexv[i][1] - dpannexv[i][2]] + dpmainw[i] + dpannexw[i][1] + dpannexw[i][2]);
#include <bits/stdc++.h>
using namespace std;
const int N = 4e4 + 10;
int dpmainv[N], dpmainw[N];
int dpannexv[N][3], dpannexw[N][3];
int cnt[N];
int dp[N];
int main()
{
int n, m;
scanf("%d%d", &n, &m);
int a, b, q;
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d", &a, &b, &q);
if (q == 0)
{
dpmainv[i] = a;
dpmainw[i] = a * b;
}
else
{
cnt[q]++;
dpannexv[q][cnt[q]] = a;
dpannexw[q][cnt[q]] = a * b;
}
}
for (int i = 1; i <= m; i++)
{
for (int j = n; j >= dpmainv[i] && dpmainv[i] > 0; j--)
{
dp[j] = max(dp[j], dp[j - dpmainv[i]] + dpmainw[i]);
if (j >= dpmainv[i] + dpannexv[i][1])
{
dp[j] = max(dp[j], dp[j - dpmainv[i] - dpannexv[i][1]] + dpmainw[i] + dpannexw[i][1]);
}
if (j >= dpmainv[i] + dpannexv[i][2])
{
dp[j] = max(dp[j], dp[j - dpmainv[i] - dpannexv[i][2]] + dpmainw[i] + dpannexw[i][2]);
}
if (j >= dpmainv[i] + dpannexv[i][1] + dpannexv[i][2])
{
dp[j] = max(dp[j], dp[j - dpmainv[i] - dpannexv[i][1] - dpannexv[i][2]] + dpmainw[i] + dpannexw[i][1] + dpannexw[i][2]);
}
}
}
printf("%d\n", dp[n]);
return 0;
}
洛谷 P1352 没有上司的舞会
思路:
经典的树形dp
设
dp[ans][0]表示以ans为根的子树,且ans不参加舞会的最大快乐值
dp[ans][1]表示以ans为根的子树,且ans参加了舞会的最大快乐值
则dp[ans][0]+=max(dp[cnt][0], dp[cnt][1]);(cnt是ans的儿子)
dp[ans][1] += dp[cnt][0]; (cnt是ans的儿子)
这题首先找到根几点,然后dfs向下遍历,dp转移就行了
#include <bits/stdc++.h>
using namespace std;
const int N = 6e3 + 10;
int a[N];
int dp[N][2];
int vis[N];
vector<int> v[N];
void dfs(int ans)
{
dp[ans][1] = a[ans];
for (int i = 0; i < v[ans].size(); i++)
{
int cnt = v[ans][i];
dfs(cnt); //往下遍历
dp[ans][0] += max(dp[cnt][0], dp[cnt][1]); //他的下属参加还是不参加
dp[ans][1] += dp[cnt][0]; //此时下属不能参加
}
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
}
int p, q;
for (int i = 1; i <= n - 1; i++)
{
scanf("%d%d", &p, &q);
v[q].push_back(p);
vis[p] = 1; //把职员标记上
}
int ans = 0;
for (int i = 1; i <= n; i++)
{
if (vis[i] == 0) //找最高职位,也就是树的根
{
ans = i;
break;
}
}
dfs(ans);
printf("%d\n", max(dp[ans][0], dp[ans][1]));
return 0;
}
能量项链
思路:一道典型的区间dp题,对于一般的n颗珠子,我们求它的最大能量值。我们可以把它的上一步的时候的组成分为dp[1]max,dp[n-1]max; dp[2]max,dp[n-2]max; dp[3]max,dp[n-3]max ……dp[n]max,dp[1]max;它右这n种情况的合并而成我们只要求出这n种情况的最大值即可求出n颗珠子时的能量的最大值。但是别忘了,题目是没有说起点在哪里的,也没有说方向是向哪里的,因此我们还得以每一个点为起点做一次分组算一遍。
状态转移方程:dp[l][r] = max(dp[l][r], dp[l][k] + dp[k][r] + a[l] * a[k] * a[r]);
#include <iostream>
#include <cstdio>
#include <cstring>
#include <math.h>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 2e2 + 10;
int a[N];
int dp[N][N];
int main()
{
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
a[i + n] = a[i];
}
// 2 3 5 10 2 3 5 10
//还是环形石子问题
for (int len = 3; len <= n + 1; len++) //最少要三个值才能聚合两个珠子,但最后一个值最后还会用到
{
for (int l = 1; l + len - 1 <= 2 * n; l++)
{
int r = l + len - 1;
for (int k = l + 1; k < r; k++) //由于每颗珠子分首尾,所以k最小也得是l+1
{
dp[l][r] = max(dp[l][r], dp[l][k] + dp[k][r] + a[l] * a[k] * a[r]); //由于k位置这个元素会重复用到,所以不用加一!!!
}
}
}
int maxn = 0;
for (int i = 1; i <= n; i++)
{
maxn = max(maxn, dp[i][n + i]);
// printf("%d\n", dp[i][i + n]);
}
printf("%d\n", maxn);
return 0;
}
思路:01背包+第k最优解
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int dp[N][N];
int a[108], b[108];
int c[108], d[108];
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
memset(dp, 0, sizeof(dp));
memset(c, 0, sizeof(c));
memset(d, 0, sizeof(d));
int n, v, p;
scanf("%d%d%d", &n, &v, &p);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; i++)
{
scanf("%d", &b[i]);
}
for (int i = 1; i <= n; i++)
{
for (int j = v; j >= b[i]; j--)
{
for (int k = 1; k <= p; k++)
{
c[k] = dp[j][k]; //体积为j时的第k大价值
d[k] = dp[j - b[i]][k] + a[i];
}
int q = 1, l = 1, r = 1;
while (q <= p && (l <= p || r <= p))
{
if (c[l] >= d[r]) //比较两个数的大小,谁大就用谁赋值
{
dp[j][q] = c[l++];
}
else
{
dp[j][q] = d[r++];
}
if (dp[j][q] != dp[j][q - 1]) //这里要去一下重
{
q++;
}
}
}
}
printf("%d\n", dp[v][p]);
}
return 0;
}
思路:根据线段树 父亲节点的区间 和 孩子节点的 区间的关系,可得知 若孩子节点是[l,r],则父亲节点有4种情况即[l - 1 - (r - l), r],[l, r + 1 + r - l],[l - 1 - (r - l) - 1, r],[l, r + 1 + r - l - 1]
然后暴力搜索即可,注意当r >= ans和l - 1 - (r - l) < 0时就要return;
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll ans;
void dfs(ll l, ll r) //建立左区间和右区间递归搜索
{
if (l == 0)
{
if (r != 0)
{
ans = min(ans, r);
}
}
if (r >= ans) //右边界
{
return;
}
if (l - 1 - (r - l) < 0) //左边界
{
return;
}
dfs(l - 1 - (r - l), r); //奇数区间
dfs(l, r + 1 + r - l);
dfs(l - 1 - (r - l) - 1, r); //偶数区间
dfs(l, r + 1 + r - l - 1);
}
int main()
{
ll l, r;
while (scanf("%lld%lld", &l, &r) != EOF)
{
ans = 0x3f3f3f3f;
dfs(l, r);
if (l == 0 && r == 0)
{
printf("0\n");
}
else
{
if (ans == 0x3f3f3f3f)
{
printf("-1\n");
}
else
{
printf("%lld\n", ans);
}
}
}
return 0;
}