01背包
这是一道有限制选择问题,可以类比01背包的思路来考虑这道题
![WechatIMG828.png](https://cdn.acwing.com/media/article/image/2023/04/04/189929_00c73e47d2-WechatIMG828.png)
#include <cstring>
#include <iostream>
using namespace std;
const int N = 110;
//用滚动数组进行空间的优化
int f[2][N], w[N], n, k;
int main()
{
cin >> n >> k;
for(int i = 1; i <= n; ++ i) cin >> w[i];
//将其他状态均设置为不合法状态,初始状态只有f[0][0] = 0
memset(f, -0x3f, sizeof f);
//错误的初始化方法,当一个都不选的话,mod k的余数只能是0
// for(int i = 0; i < k; ++ i) f[0][i] = 0;
f[0][0] = 0;
for(int i = 1; i <=n ; ++ i)
for(int j = 0; j < k; ++ j)
f[i & 1][j] = max(f[(i - 1) & 1][j], f[(i - 1) & 1][(j + k - w[i] % k) % k] + w[i]);
cout << f[n & 1][0] << endl;
return 0;
}
完全背包
裴蜀定理
对于任意整数a,b,存在一对整数x,y,满足ax+by=gcd(a, b),推广至n个数也成立
当这n个数的最大公约数(记为d)不为一时,我们能凑出来的数,只能是这n个数的最大公约数的倍数,所以所有因数不包含d的数一定不能被这n个数凑出来,并且这个数的倍数(不包含d因子)的数也都不能被凑出来,所以当最大公约数不是1时答案一定是INF
当最大公约数是1时,我们能凑出的数一定是1的倍数,我们知道1是任何数的公因子,所以此时,只有有限个数不能被凑出来。
那么最大的不能凑出来的数是多少?我们知道对于两个数而言gcd(a, b) = 1;a,b不能凑出来的最大的数是(a - 1)*(b - 1) - 1,而数越多,我们能凑出来的数就越多,这个上界也将越小,所以我们寻找100以内两个最大的互质的数99, 98,所以我们将上界定位1e4即可(当然更大也行,不过要考虑一下时间复杂度,不能超时了),分析自此我们这道题的难点已经解决了,剩下的就是跑一遍完全背包即可。
完全背包闫氏dp分析法
![WechatIMG840.png](https://cdn.acwing.com/media/article/image/2023/04/06/189929_4da54ad2d4-WechatIMG840.png)
关于完全背包的优化,可以用滚动数组,也可以直接优化至一维,我们用到的状态有f[i - 1][j]和f[i][j - wi],因为我们在更新时需要用到体积更大的状态是未更新的,应该正序循环体积
当我们枚举只j时,[0,j]存储的是第i层的状态,也就是我们已经更新过的状态,[j, M]存储的是第i - 1层的状态。
二维未优化
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 110, M = 1e5 + 10;
//f[i][j]表示考虑前i个物品,总体积恰好为j的所有方案
int n, w[N], f[N][M];
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}
int main()
{
cin >> n;
int d = 0;
for(int i = 1; i<= n; ++ i)
{
scanf("%d", &w[i]);
d = gcd(w[i], d);
}
if(d != 1) puts("INF");
else
{
f[0][0] = 1;
for(int i = 1; i <= n; ++ i)
for(int j = 0; j <= M; ++ j)
{
f[i][j] = f[i - 1][j];
if(j >= w[i]) f[i][j] |= f[i][j - w[i]];
}
int res = 0;
for(int i = 0; i <= M; ++ i)
if(!f[n][i]) res ++;
cout << res << endl;
}
return 0;
}
滚动数组优化
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 110, M = 1e5 + 10;
//f[i][j]表示考虑前i个物品,总体积恰好为j的所有方案
int n, w[N], f[2][M];
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}
int main()
{
cin >> n;
int d = 0;
for(int i = 1; i<= n; ++ i)
{
scanf("%d", &w[i]);
d = gcd(w[i], d);
}
if(d != 1) puts("INF");
else
{
f[0 & 1][0] = 1;
for(int i = 1; i <= n; ++ i)
for(int j = 0; j <= M; ++ j)
{
f[i & 1][j] |= f[(i - 1) & 1][j];
if(j >= w[i])
f[i & 1][j] |= f[i & 1][j - w[i]];
}
int res = 0;
for(int i = 0; i <= M; ++ i)
if(!f[n & 1][i]) res ++;
cout << res << endl;
}
return 0;
}
一维
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 110, M = 1e5 + 10;
//f[i][j]表示考虑前i个物品,总体积恰好为j的所有方案
int n, w[N], f[M];
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}
int main()
{
cin >> n;
int d = 0;
for(int i = 1; i<= n; ++ i)
{
scanf("%d", &w[i]);
d = gcd(w[i], d);
}
if(d != 1) puts("INF");
else
{
f[0] = 1;
for(int i = 1; i <= n; ++ i)
for(int j = w[i]; j <= M; ++ j)
{
f[j] |= f[j - w[i]];
}
int res = 0;
for(int i = 0; i <= M; ++ i)
if(!f[i]) res ++;
cout << res << endl;
}
return 0;
}