算法
(DP,分组背包问题) $O(Nm)$
可以将每个主件及其附件看作一个物品组,记主件为 $p$,两个附件为 $a, b$,则最多一共有4种组合:
- $p$
- $p, a$
- $p, b$
- $p, a, b$
这四种组合是互斥的,最多只能从中选一种,因此可以将每种组合看作一个物品,那么问题就变成了分组背包问题。可以参考AcWing 9. 分组背包问题。
在枚举四种组合时可以使用二进制的思想,可以简化代码。
时间复杂度
分组背包的时间复杂度是 物品总数 * 总体积,因此总时间复杂度是 $O(Nm)$。
C++ 代码
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#define v first
#define w second
using namespace std;
typedef pair<int, int> PII;
const int N = 60, M = 32010;
int n, m;
PII master[N];
vector<PII> servent[N];
int f[M];
int main()
{
cin >> m >> n;
for (int i = 1; i <= n; i ++ )
{
int v, p, q;
cin >> v >> p >> q;
p *= v;
if (!q) master[i] = {v, p};
else servent[q].push_back({v, p});
}
for (int i = 1; i <= n; i ++ )
for (int u = m; u >= 0; u -- )
{
for (int j = 0; j < 1 << servent[i].size(); j ++ )
{
int v = master[i].v, w = master[i].w;
for (int k = 0; k < servent[i].size(); k ++ )
if (j >> k & 1)
{
v += servent[i][k].v;
w += servent[i][k].w;
}
if (u >= v) f[u] = max(f[u], f[u - v] + w);
}
}
cout << f[m] << endl;
return 0;
}
这里二进制枚举的理解:
servent[i]只能取0、1、2,这三个值左移之后对应 –> 1、2、4 再对应到j的取值为 –> (0)、(0, 1)、(0,1,2,3)
然后在最内层循环中,k同样也对应 0、1、2(补充:j的值0、1、2、3 对应到二进制,值分别为 00、01、10、11)
j >> k & 1 意思是 当下方案j情况下,对应的第k个附件是选还是不选(j 此时看做一种方案,即每个位 上0对应不选,1对应选)
我终于懂了,谢谢大佬的解释orz
应该是servent[i].size() 取值范围为[0, 2],j的取值范围[0, 3], k的取值范围[0,1]
当j = 0时:k 无论为0还是1,都不取
当j = 1时:k = 0 取, k = 1 不取
当j = 2时:k = 0 不取,k = 1 取
当j = 3时:k = 0 || k = 1 都取
分别对应四种互斥情况
二进制枚举太厉害了。。
确实tql
请问可不可以不用二进制优化?比如像下面这样
二进制优化 是通解 你这个和二进制没什么区别 只不过更加具体
这就是二进制枚举
互斥tql
这里
是不是有master[i]与servent[i]同时为空的情况,这样是不是会多循环。
请问一下我这个代码是不是因为数据太水了呀
就是能过,我去洛谷交了一发也是ac的
我还不是交了的。。。(这样你会不会棕啊)。。。
无所谓,反正又不是比赛
想问下大佬为什么能将j ++ 或者 j – 写成 j += cnt 或者 j -= cnt呀
因为这里我求的是gcd,所以在j+1到j+cnt之间一定没有数,就不用遍历了
现在的数据,我用这样的做法一直tle。。。
数据已经加强了
还是能过
你是不是因为没加cnt啊
话说为什么这种公因数优化是正确的,我添加了 cnt 变量之后,发现 TLE 变成了 AC
这个我解释了的啊
我其实是想问,从复杂度来说,添加 gcd 之后,复杂度会下降多少,为什么数据量这么大也可以通过。
会除以cnt^2
### java版本
https://cdn.acwing.com/static/web/img/icons/emoji/thumbsup.png
如果一个包含附件的主件,主件价格为0,附件价格不为0,该情况好像在上述代码中会被直接忽略
已修正~
这样可能会只计算附件的价格不管主件的价格 因为题中重要度是1-5 判断一下重要度是否为0就行了吧 如果为0 说明是附件 就跳过
%%%
# Or2
请问一下为什么不可以选多个主件
请问这样写有什么问题呢?
有近一半数据过不去
二维数组的第一个存主件,第二个第三个存储可能存在的附件
同问,过不去7430那个样例
原因是你的cnt和c不一致,附件无法准确归属到主件(附件归属到了错误的主件)
j >> k & 1这是什么意思啊,一直没看明白
二进制做法,跟状态压缩差不多,就是比如有四个物品,每个物品都看有拿或者不拿两种状态,刚好也就是二进制中的0或者1,然后这四个物品每个物品都有这样的一种状态,然后这个j就是当前十进制的物品状态,如果你要看第k个物品的状态是拿了还是没有拿,你就直接把找到当前物品的状态再右移相应的位数就可以了,这里因为加加上每个物品的状态,所以要判断该物品拿了还是没有拿
明白了,谢谢orz
谢谢兄弟,解释的很清晰。
妙啊
为啥用二维来写就是错的,优化成一维反而对了
应该是当 i是附件的时候f[i][j]不会去更新,i+1的时候用f[i-1]就会出问题吧
二维代码没有问题:
你的写法,应该这样修改:
为什么
if(master[i].v)
这一段去掉之后不影响结果呢,恕我脑子笨,不明白那一步能把主件为0附件不为0的情况忽略掉可能使题目数据弱了,y总在这个题的视频切片底下说了不能加
if(master[i].v)
因为可能有主件价值为0但附件价值不为0的情况想问一下,如果把这个dp写成二维是否还能枚举1到n ^^
可以参考这篇题解,使用了一种类似01背包的形式写的 :https://www.acwing.com/solution/content/16430/
这是那片题解评论区里的一个代码:
Enemy
我刚刚用y总的方法试了一下二维的分组背包,是可以的
哦哦,谢谢 但是我有个问题 把该dp写成二维在枚举1到n的时候 若遇到主件这可以进到下面的 k循环,但是在遇到状态转移方程f[i][j]=max(f[i][j],f[i-1][j-v]+w);这个的时候怎么解释呢,i-1不一定代表着是主件啊,也有可能是附件,那这样的dp是什么意思呢,我想的是为什么不枚举每一组(也就是每个主件)而枚举1到n确是对的呢
如果物品是附件的话,是不会被计入$g$数组的,$f[i][j]$代表的是在前i个物品中选总体积为j时的最大价值,如果当前物品$i$为附件的话,$g[i]$={0,0},执行
f[i][j]=max(f[i][j],f[i-1][j-v]+w);
实际上就是执行f[i][j]=max(f[i][j],f[i-1][j]);
遇到附件时也会进入k循环,因为1<<0=1,此时v=0,w=0
哦哦,确实能进入k循环,但是不会进入第四成循环,但是我想问一下如果主件在进行状态转移的时候 f[i-1]的含义是什么,i-1不一定是主件啊有可能是附件,可以这样枚举吗,不应该是枚举每一组吗也就是枚举主件吗
i-1不一定时主件,如果i-1是附件的话也会被它上一个主件的值更新掉,因为$f[i][j]$代表的前i个里的最大价值,$f[i][j]=max(f[i][j],f[i-1][j])$一定会让当前物品为附件时的最大价值更新为上一次是主件时的最大价值。
可以模拟一下这两个样例:
1000 2
1001 1 0
10 1 1
和
1000 2
10 1 0
10 1 1
(我语文不好可能表述的不清楚)
嗯嗯,谢谢
%%%%%
唔 为什么这里省掉了视频中的
if(master[i].v)
呢噢噢 刚刚又去仔细地读了下题 应该是题目约束输入时附件必须出现在该主件的后面输入对吧
我看到下面有个评论是:“如果一个包含附件的主件,主件价格为0,附件价格不为0,该情况好像在上述代码中会被直接忽略”。去掉
if(master[i].v)
应该是补上了这种情况吧。雀食 , 刚刚去试了下 就把样例的数据的第一个主件的价格改为0,加 / 不加
if (master[i].v)
是两种结果,但是加了还是a了–>数据应该还没加强~j >> k & 1
这里是啥意思
表示选取了主件i 的附件集合中的第k个物品