完全背包变种

Posted on 2023-03-01 20:53  lyc2002  阅读(14)  评论(0编辑  收藏  举报

[acwing]1023.小明买书

/*
	dp[i][j] 表示只考虑前 i 个物品,其价值恰好为 j 的方案个数
	
	dp[i][j] 可从选多少个第 i 个物品推导出来,假设最多能选 s 个
	如果选 0 个第 i 个物品,dp[i][j] = dp[i - 1][j - vi * 0]
	如果选 1 个第 i 个物品,dp[i][j] = dp[i - 1][j - vi * 1]
	......
	如果选 s 个第 i 个物品,dp[i][j] = dp[i - 1][j - vi * s]
	综上,
	dp[i][j] = dp[i - 1][j - vi * 0] + dp[i - 1][j - vi * 1] + ... + dp[i - 1][j - vi * s]
	那么,
	dp[i][j - vi] = dp[i - 1][j - vi * 1] + dp[i - 1][j - vi * 2] + ... + dp[i - 1][j - vi * s]
	即,
	dp[i][j] = dp[i - 1][j] + dp[i][j - vi]
	
	遍历顺序应该是从上到下 i[1, 4],从左到右的 j[0, n]
	
	初始化 dp[0][0] = 1
	
	目标 dp[4][n]
*/
#include <cstdio>

using namespace std;

const int N = 1010;

int n;
int v[] = {-1, 10, 20, 50, 100};
int dp[5][N];

int main()
{
    scanf("%d", &n);
    
    dp[0][0] = 1;
    
    for (int i = 1; i <= 4; i++)
        for (int j = 0; j <= n; j++) {
            dp[i][j] = dp[i - 1][j];
            if (j >= v[i]) dp[i][j] += dp[i][j - v[i]];
        }
        
    printf("%d", dp[4][n]);        
    
    return 0;
}
/*
	空间优化
*/
#include <cstdio>

using namespace std;

const int N = 1010;

int n;
int v[] = {-1, 10, 20, 50, 100};
int dp[N];

int main()
{
    scanf("%d", &n);
    
    dp[0] = 1;
    
    for (int i = 1; i <= 4; i++)
        for (int j = 0; j <= n; j++) {
            if (j >= v[i]) dp[j] += dp[j - v[i]];
        }
        
    printf("%d", dp[n]);        
    
    return 0;
}

[acwing]1050.鸣人的影分身

/*
	dp[i][j][k] 表示只考虑前 i 个物品,其价值恰好为 j,且所选物品个数为 k 的方案的数量
	
	dp[i][j][k] 可从选多少个第 i 个物品推导出来,假设最多能选 s 个
	如果选 0 个第 i 个物品,dp[i][j][k] = dp[i - 1][j - vi * 0][k - 0]
	如果选 1 个第 i 个物品,dp[i][j][k] = dp[i - 1][j - vi * 1][k - 1]
	......
	如果选 s 个第 i 个物品,dp[i][j][k] = dp[i - 1][j - vi * s][k - s]
	综上,
	dp[i][j][k] = dp[i - 1][j - vi * 0][k - 0] + dp[i - 1][j - vi * 1][k - 1] + ... + dp[i - 1][j - vi * s][k - s]
	那么,
	dp[i][j - vi][k - 1] = dp[i - 1][j - vi * 1][k - 1] + dp[i - 1][j - vi * 2][k - 2] + ... + dp[i - 1][j - vi * s][k - s]
	即,
	dp[i][j][k] = dp[i - 1][j][k] + dp[i][j - vi][k - 1];
	
	遍历顺序应该是 i[1, m + 1],j[0, m],k[0, n]
	
	初始化 dp[0][0][0] = 1
	
	目标 dp[m + 1][m][n]
*/
#include <cstdio>
#include <cstring>

using namespace std;

const int N = 20;

int t, m, n;
int dp[N][N][N];

int main()
{
    scanf("%d", &t);
    while (t--) {
        memset(dp, 0, sizeof(dp));
        scanf("%d%d", &m, &n);
        
        dp[0][0][0] = 1;
        
        for (int i = 1; i <= m + 1; i++)
            for (int j = 0; j <= m; j++)
                for (int k = 0; k <= n; k++) {
                    int vi = i - 1;
                    dp[i][j][k] = dp[i - 1][j][k];
                    if (j - vi >= 0 && k - 1 >= 0) dp[i][j][k] += dp[i][j - vi][k - 1];
                }
                
        printf("%d\n", dp[m + 1][m][n]);
    }
    
    return 0;
}
/*
	空间优化
*/
#include <cstdio>
#include <cstring>

using namespace std;

const int N = 20;

int t, m, n;
int dp[N][N];

int main()
{
    scanf("%d", &t);
    while (t--) {
        memset(dp, 0, sizeof(dp));
        scanf("%d%d", &m, &n);
        
        dp[0][0] = 1;
        
        for (int i = 1; i <= m + 1; i++)
            for (int j = 0; j <= m; j++)
                for (int k = 0; k <= n; k++) {
                    int vi = i - 1;
                    if (j - vi >= 0 && k - 1 >= 0) dp[j][k] += dp[j - vi][k - 1];
                }
                
        printf("%d\n", dp[m][n]);
    }
    
    return 0;
}

[acwing]1226.包子凑数

/*
    由贝祖定理知如果 n 个数的最大公因数不为 1,那么将有无数个数无法表示(因为只能表示 gcd 的倍数)
    
    由[AcWing]1205.买不到的数目知两个数最大不能凑出来的数为 (n - 1) * (m - 1) - 1,如果多加一个数的话,
    最大不能凑出来的数应该会变小,那么由数据范围可知其上界为 (99 - 1) * (100 - 1) - 1,这里直接取 10000
    
    dp[i][j] 表示只考虑前 i 个物品,其价值总和是否恰好为 j
    
    dp[i][j] 可从选多少个第 i 个物品推导出来,假设最多选 s 个
    如果选 0 个第 i 个物品,dp[i][j] = dp[i - 1][j - vi * 0]
    如果选 1 个第 i 个物品,dp[i][j] = dp[i - 1][j - vi * 1]
    ...
    如果选 s 个第 i 个物品,dp[i][j] = dp[i - 1][j - vi * s]
    综上, 
    dp[i][j] = dp[i - 1][j - vi * 0] | dp[i - 1][j - vi * 1] | ... | dp[i - 1][j - vi * s]
    那么,
    dp[i][j - vi] = dp[i - 1][j - vi * 1] | dp[i - 1][j - vi * 2] | ... | dp[i - 1][j - vi * s]
    即,
    dp[i][j] = dp[i - 1][j] | dp[i][j - vi]
    
    遍历顺序 i[1, n], j[0, N)
    
    初始化 dp[0][0] = 1
    
    目标 dp[n][i] 如果为 0,表示无法表示
*/
#include <cstdio>

using namespace std;

const int N = 100010;

int n;
int v[110];
int dp[110][N];

int gcd(int a, int b)
{
    return b == 0 ? a : gcd(b, a % b);
}

int main()
{
    scanf("%d", &n);
    int d = 0;
    for (int i = 1; i <= n; i++) {
        scanf("%d", &v[i]);
        d = gcd(d, v[i]);
    }
    
    if (d != 1) {
        printf("INF");
        return 0;
    } else {
        dp[0][0] = 1;
        for (int i = 1; i <= n; i++)
            for (int j = 0; j < N; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j - v[i] >= 0) dp[i][j] |= dp[i][j - v[i]];
            }
    }
    
    int res = 0;
    for (int i = 0; i < N; i++)
        if (dp[n][i] == 0)
            res++;
            
    printf("%d", res);
    
    return 0;
}
/*
	空间优化
*/
#include <cstdio>

using namespace std;

const int N = 100010;

int n;
int v[110];
int dp[N];

int gcd(int a, int b)
{
    return b == 0 ? a : gcd(b, a % b);
}

int main()
{
    scanf("%d", &n);
    int d = 0;
    for (int i = 1; i <= n; i++) {
        scanf("%d", &v[i]);
        d = gcd(d, v[i]);
    }
    
    if (d != 1) {
        printf("INF");
        return 0;
    } else {
        dp[0] = 1;
        for (int i = 1; i <= n; i++)
            for (int j = v[i]; j < N; j++)
                dp[j] |= dp[j - v[i]];
    }
    
    int res = 0;
    for (int i = 0; i < N; i++)
        if (dp[i] == 0)
            res++;
            
    printf("%d", res);
    
    return 0;
}