最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划
题目描述
总公司拥有 $M$ 台 相同 设备,准备分给下属的 $N$ 个分公司。
第 $i$ 家公司分到 $j$ 台机器后,所获的的收益为 $w_{ij}$
求一种分配方案,使得总收益最大,输出该方案
分析
本题乍一看很像是 背包DP,为了转换成 背包DP 问题,我们需要对里面的一些叙述做出 等价变换
每家公司 我们可以看一个 物品组,又因为 每家公司 最终能够被分配的 机器数量 是固定的
因此对于分给第 $i$ 个 公司 的不同 机器数量 可以分别看作是一个 物品组 内的 物品
该 物品 $k$ 的含义:分给第 $i$ 个 公司 $k$ 台机器
该 物品 $k$ 的体积:$k$
该 物品 $k$ 的价值:$w_{ik}$
于是,本题就转换成了一个 分组背包问题
直接上 分组背包 的 闫氏DP分析法
初始状态 :f[0][0]
目标状态 :f[N][M]
动态规划求状态转移路径
这里我介绍一个从 图论 角度思考的方法
动态规划 本质是在一个 拓扑图 内找最短路
我们可以把每个 状态f[i][j]
看作一个 点,状态的转移 看作一条 边,把 状态的值 理解为 最短路径长
具体如下图所示:
对于 点 f[i][j]
来说,他的 最短路径长 是通过所有到他的 边 更新出来的
更新 最短路 的 规则 因题而已,本题的 更新规则 是 $f(i,j)=\max\{f(i-1,j-v_i)+w_i\}$
最终,我们会把从 初始状态(起点)到 目标状态 (终点)的 最短路径长 更新出来
随着这个更新的过程,也就在整个 图 中生成了一颗 最短路径树
该 最短路径树 上 起点 到 终点 的 路径 就是我们要求的 动态规划的状态转移路径
如下图所示:
那么 动态规划求状态转移路径 就变成了在 拓扑图 中找 最短路径 的问题了
可以直接沿用 最短路 输出路径的方法就可以找出 状态的转移
Code
找 最短路径 递归写法
#include <iostream>
using namespace std;
const int N = 20;
int n, m;
int w[N][N];
int f[N][N];
int path[N], cnt;
void dfs(int i, int j)
{
if (!i) return;
//寻找当前状态f[i][j]是从上述哪一个f[i-1][k]状态转移过来的
for (int a = 0; a <= j; ++ a)
{
if (f[i - 1][j - a] + w[i][a] == f[i][j])
{
path[cnt ++ ] = a;
dfs(i - 1, j - a);
return;
}
}
}
int main()
{
//input
cin >> n >> m;
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j)
cin >> w[i][j];
//dp
for (int i = 1; i <= n; ++ i)
for (int j = 1; j <= m; ++ j)
for (int k = 0; k <= j; ++ k)
f[i][j] = max(f[i][j], f[i - 1][j - k] + w[i][k]);
cout << f[n][m] << endl;
//find path
dfs(n, m);
for (int i = cnt - 1, id = 1; i >= 0; -- i, ++ id)
cout << id << " " << path[i] << endl;
return 0;
}
妙啊,作者真他娘的是个人才!
为什么最后是要倒序输出路径啊???
是因为是从最终目标状态开始递归的吗??
对的
闫氏dp分析法的集合和属性似乎反了
一本通的那个第二个点我也错了
测试点//input
2 15
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
测试点//output
2
1 0
2 15
dfs代码//output
2
1 1
2 1
special judge没写好吧
这题不要求字典序最小的,想要字典序最小倒着枚举就行
acwing上可以过
Acwing的题解质量是其魅力之一
#include[HTML_REMOVED]
#include[HTML_REMOVED]
using namespace std;
const int N = 11,M = 16;
int n,m;
int w[N][M];
int f[N][M];
int path[N],cnt = 1;
void dfs(int i,int j)
{
if(!i) return;
}
int main()
{
cin>>n>>m;
}
狠狠地点了,理解得很透彻~~
666
有些好奇,这种dfs的写法感觉和y总的写法是同一个意思,是会比y总那样找出答案路径更快一些吗?
时间复杂度应该是一样的
你好 你的代码 在一本通交wa了一个点
测试点//input
2 15
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
测试点//output
2
1 0
2 15
dfs代码//output
2
1 1
2 1
special judge没写好吧
dfs这样记录,输出会方便一点
建议这样:
最后一个点过不去
这个能不能贪心优化到nlogn呢?
每次的体积都是1,能不能堆优化
好像可以用最短路堆优化,但是这里我不知道怎么贪心考虑,贪心感觉好玄学😐😐
兄弟太强了,文章真的对理解知识点将的很通透,太感谢了
哇,厉害,一直想总结一下,就是不会用语言总结出来。
彩色铅笔yyds
谢谢支持hh
彩铅巨巨赛高!
Peter佬赛高!