洛谷

洛谷题单指南-状态压缩动态规划-P1441 砝码称重

原题链接:https://www.luogu.com.cn/problem/P1441

题意解读:n个砝码去掉m个能称出的重量数。

解题思路:

1、暴力法

用二进制整数1~2^n - 1表示选取砝码的状态,取0的个数是m的状态表示去掉m个,然后针对剩下的砝码通过dfs枚举出所有可能的重量,每种状态求得的重量数取max。

时间复杂度:最大C(20,4) 2^16 ≈ 310^8,显然无法通过。

60分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 25, M = 2005;

int a[N]; 
set<int> s;
int n, m, ans;

//计算二进制中0的个数
int count(int x)
{
    int cnt = 0;
    for(int i = 0; i < n; i++)
        if(!(x >> i & 1))
            cnt++;
    return cnt;
}

void dfs(int k, int sum, int state)
{
    if(sum) s.insert(sum);
    if(k == n) return;
    dfs(k + 1, sum, state); 
    if(state >> k & 1) dfs(k + 1, sum + a[k], state); 
}

int main()
{
    cin >> n >> m;
    for(int i = 0; i < n; i++) cin >> a[i];
    for(int i = 1; i < 1 << n; i++)
    {
        if(count(i) == m)
        {
            s.clear();
            dfs(0, 0, i);
            ans = max(ans, (int)s.size());
        }
    }
    cout << ans;
    return 0;
}

2、优化暴搜

问题的关键在于如何求给定数量的砝码能称出的重量数。

砝码数量最多20,每个砝码重量最大100,也就是能称出的最大重量不超过2000,那么可以考虑枚举所有砝码,根据前i个砝码能否称出重量j,枚举重量j,实现状态的转移。

设f[i][j]=1表示前i个砝码能称出重量j,

如果不选第i个砝码,有f[i][j] = f[i - 1][j];如果选第i个砝码,有f[i][j] = f[i - 1][j - a[i]]。

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 25, M = 2005;

int a[N];
bool f[N][M]; //f[i][j]=1表示前i个砝码能称出重量j
int n, m, ans;

//计算二进制中0的个数
int count(int x)
{
    int cnt = 0;
    for(int i = 0; i < n; i++)
        if(!(x >> i & 1))
            cnt++;
    return cnt;
}

int dp(int stat)
{
    int res = 0;
    memset(f, 0, sizeof(f));
    for(int i = 0; i <= n; i++)
        f[i][0] = true;
    for(int i = 1; i <= n; i++) //枚举所有砝码
    {
        for(int j = 0; j <= 2000; j++) //枚举所有当前砝码可称出的重量
        {
            f[i][j] = f[i - 1][j];
            if((stat >> i - 1 & 1) && j - a[i] >= 0)
                f[i][j] |= f[i - 1][j - a[i]];
        }
    }
    for(int i = 1; i <= 2000; i++) res += f[n][i];
    return res;
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i < 1 << n; i++)
    {
        if(count(i) == m)
        {

            ans = max(ans, dp(i));
        }
    }
    cout << ans;
    return 0;
}

进一步优化为一维,f[i]表示能否称出重量i

注意重量的枚举要改成从大到小,状态的转移要增加判断条件

100分代码:

#include <bits/stdc++.h>
using namespace std;

const int N = 25, M = 2005;

int a[N];
bool f[M]; //f[i]=1表示能称出重量i
int n, m, ans;

//计算二进制中0的个数
int count(int x)
{
    int cnt = 0;
    for(int i = 0; i < n; i++)
        if(!(x >> i & 1))
            cnt++;
    return cnt;
}

int dp(int stat)
{
    int res = 0;
    memset(f, 0, sizeof(f));
    f[0] = 1;
    for(int i = 1; i <= n; i++) //枚举所有砝码
    {
        for(int j = 2000; j >= 0; j--) //枚举所有当前砝码可称出的重量
        {
            if((stat >> i - 1 & 1) && j - a[i] >= 0 && f[j - a[i]])
                f[j] = f[j - a[i]];
        }
    }
    for(int i = 1; i <= 2000; i++) res += f[i];
    return res;
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i < 1 << n; i++)
    {
        if(count(i) == m)
        {

            ans = max(ans, dp(i));
        }
    }
    cout << ans;
    return 0;
}