这道题状态定义和转移与另外一道很相似传送门
这也是背包问题,有限制的组合问题。
所以二维状态,第一维是物品数量,第二维是限制。
1.状态定义:
$dp(i, j)$代表前$i$个物品总价值$ \%k=j $的集合,在这里限制是关于 $\%k$余数是多少。
2.转移方程:
$ dp(i, j) = max(dp(i - 1, j), dp(i - 1, (j - w[i]) \%k)+w[i]) $
我觉得这里用集合分析的方法分析更好,特别是在状态转移的时候,集合究竟由哪些子集合组成。
筛选标准:最后不同的一步(在这里是选不选这个物品)
不选的话:子集合都不包括第$i$个物品,表示的意义就是前$i-1$个物品,$\%k=j$的集合:$dp(i-1, j)$。
选择第$i$个物品:子集合都包括第$i$个物品,并且前面选择的商品和$S$满足$(S+a[i])\%k=j$,而我们需要从和这个$S$有关的集合,状态记录的都是$\%k$后的结果,所以我们只需要知道$S\%k (=(j-a[i])\%k)$的那个状态的值。
3.初始化:
$dp(0, 0) = 0, dp(0, i) (i \ne 0)$都是不合法的状态,所以必须要初始化为负无穷,不然的话可以选择先初始化第一件物品的所有情况,再迭代第二个物品之后 (仔细想想,如果不合法的状态初始化为0了,后面递推的时候,答案就会变大)
4.注意事项:
(1)转移方程中的$(j - w[i]) \%k)$可能为负的,必须要将余数变成$[0,n-1]$之间,所以$cpp$负数取余要变成$(j - w[i]) \%k+k)\%k$。
下面大致说明下正确性:
1.假如$(j - w[i] >= 0)$,由于$k\%k=0$,所以不会影响。
2.假如$(j - w[i] < 0)$, 带个负数进去试下就可以验证,在这里$+k$可以使$(j - w[i]) \%k$变成$[0,n-1]$之间,再取余仍然是$[0,n-1]$之间。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<string>
#include<map>
#include<set>
#include<unordered_map>
#include<unordered_set>
#include<queue>
#include<stack>
#include<climits>
#define x first
#define y second
#define MP make_pair
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
const int N = 100 + 5;
int n, k, w[N];
//dp(i, j)代表前i个物品 总价值%k=j的集合
int dp[N][N];
int main(void){
cin >> n >> k;
for (int i = 1; i <= n; ++i) cin >> w[i];
for (int i = 0; i < N; ++i)
for (int j = 0; j < N; ++j)
dp[i][j] = INT_MIN;
//dp(0 ,i) (i != 1)都是不存在的状态,所以必须要初始化为负无穷,不然的话可以选择先初始化第一件物品的所有情况,再迭代第二个物品之后
//dp(i ,j) <- max(dp(i - 1, j), dp(i - 1, (j - w[i]) % k) + w[i])
// (s + w[i]) % k = j -> s % k = (j - w[i]) % k
dp[0][0] = 0;
for (int i = 1; i <= n; ++i){
for (int j = 0; j <= k - 1; ++j)
dp[i][j] = max(dp[i - 1][j], dp[i - 1][((j - w[i]) % k + k) % k] + w[i]);
}
cout << dp[n][0];
return 0;
}
为什么(S+a[i])%k=j就可以转化为S%k(=(j−a[i])%k)?
虽然是道简单题,但是想要AC却要注意许多细节啊
提醒一下有和我同样疑问的同学
1.这题余数一定要在0到k-1之间,所以(1)转移方程中的(j−w[i])%k)可能为负的,必须要将余数变成[0,k−1]之间,cpp负数取余要变成(j−w[i])%k+k)%k(j−w[i])%k+k)%k。
2.同余定理:x%mod=(x+mod)%mod
这里运用了整体思想,(j-w[i])%k是求的,但可能为负,就转为正,所以把他看为一个整体,(x+k)%k.
好狠的include啊
还是不太理解为什么要把除了dp(0,0)=0 以外的dp(0,i)都初始化为负无穷,求大佬再解释一下
因为从状态表示来看,在前0个糖果中选得的糖果总和除以k的余数为1还是2还是多少,其本身就是无意义的,而后面要取最大值,f[i][j]=max(f[i-1][j],f[i-1][(j+k-w[i]%k)%k]+w[i]);这也是为了不影响当i为1时的结果
好的好的,非常感谢~~
那为什么01背包就不用把除了dp(0,0)=0 以外的dp(0,i)都初始化为负无穷呢,也是非法的呀
我是这样理解的:
除了dp(0,0),dp(0,j)也不是非法,他的状态表示是从前0个物品中选且价值不超过j的个数,其值为0,是有值的。
而这题中非法是因为从前i个糖果不可能取到余数为正的情况,是不存在值的。
赞同!刚刚想了好久才想明白这一点.....
hhh一起加油
大佬,dp(0,i)是无意义的,只需要将dp(0,i)初始化为负无穷就可以了,为什么要将整个数组初始化为负无穷呢
这样也是可以的 后面的由前面的覆盖,我感觉只需要将dp(0,j)初始化,后面的根据转移方程可以自己算出
第二个维度是为什么是模上k的余数,可以讲解一下思路吗
一维数组怎么去做
mambaout,what can i say
这种题的状态定义怎么想啊,靠直觉吗?
为什么是加wi,wi不是糖果数目吗?不是加1,明明求的是最大方案数啊
为什么我用 memset(dp,-2e9,sizeof dp);就是错的,用for循环遍历就对了啊?
memset用法的原因
为什么要加W[I]
这个相当于你在前i-1个中选一个与w[i]%k的余数相加等于j的当前最优(即最大的方案),dp(i - 1,(j - w[i]) % k)相当于求前i - 1个中模k的余数与此时选第i个物品模k的余数相加等于j的情况,就是找一个能和第i个物品加起来模k余数为j的情况。
为什么, 用abs不行
为什么(S+a[i])%k=j就可以转化为S%k(=(j−a[i])%k)?
因为(S+a[i])%k=j就说明 S+a[i]=nk+j (n是某个整数)
变换一下就是 S=nk+j-a[i] ------> S%k=(j-a[i])%k (nk%k=0)
多谢大佬指点
借楼问一下为什么最后可以直接输出f[n][0]而不用去管题目最后一句话“如果不能达到 K 的倍数这一要求,输出 0”
因为(a+b)%p=(a%p+b%p)%p,S=nk+j-a[i]推出来的不应该是S%k=(j-a[i])%k%k吗?不太懂为什么少了个%k呢
因为f[n][0]就表示从前n个数中选,且模K余数为0。自然就满足了是K的倍数啦
求两次%k 和 求一次%k 结果是一样的吧
额 那可以理解为如果找不到满足达到k的倍数的选法,那么f[n][0]最后的值其实就是0对吗..
为什么(S+a[i])%k=j就可以转化为S%k(=(j−a[i])%k)?
这个include 给我整乐了
你是要再整个acwing出来吗?
这个include有些炸裂😂
为什么(S+a[i])%k=j就可以转化为S%k(=(j−a[i])%k)?
为什么不可以让f[0][0]等于0 呢?表示从前0个物品中选,选出来的和模k为0,前0个物品里,选0个,模k为0,不就是应该等于1嘛
这种初始化的问题应该怎么确定啊
你这一表示啥含义呀?它这状态方程的属性存储的是所有方案中总和最大的那种,f[0][0]什么物品都没有不就是f[0][0]=0吗?
谢谢
你说的应该是方案数,这里是求最大值
好厉害