包子凑数(完全背包,数论)

原题链接:1226. 包子凑数 - AcWing题库

题干初看较晦涩,简言之:

用现有的a1、a2、a3(假如只有三笼),去凑数,如果能凑出无限个数,输出INF,反之,只能凑出有限个数,输出这些数(这道题是输出有多少个)。

此题用到裴蜀定理:若a,b是整数,且gcd(a,b)=d,那么对于任意的整数x,y,ax+by都一定是d的倍数,特别地,一定存在整数x,y,使ax+by=d成立。(这里的x、y是可以取负数的)

对于此题,若d = 1,ab最大公因数为1,ab所能凑出的数一定是1的倍数,是1的倍数的数当然有无限多个,输出INF,换言,若d != 1,一定存在有限个数不能凑出,算法求解其个数。

注意上述是两个数ab,现在我们将裴蜀定理推广至多个数,也就是一般情况,当多个数的gcd = 1,也是能凑出无限多个的,gcd != 1,势必会有有限个数不能凑出。数论有个特点:一般是特殊的辐射,无数个数是几个数的辐射;特殊是一般的缩影,几个数的本质代表了无数个数。裴蜀中的几个就是两个。

参考:AcWing 1226. 包子凑数 完全背包 ($yan氏dp + 层层分析$) - AcWing

二维dp做法:

#include <iostream>
using namespace std;

const int N = 105;
bool dp[N][10005];
int w[N];
int n;

/*
int gcd(int a,int b) { return b ? gcd(b,a%b) : a; }
*/
int gcd(int a,int b)
{
return a%b==0 ? b:gcd(b,a%b); // 欧几里得算法,这样写相较上一种好理解些
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n;
    int d = 0;
    for (int i = 1; i <= n; i++) {
        cin>>w[i];
        d = gcd(d,w[i]);
    }
    
    if (d != 1) cout<<"INF\n";
    else
    {
        dp[0][0] = true; // 初始化   
        for (int i = 1; i <= n; i++) {  // 二维完全背包模板  
            for (int j = 0; j <= 10000; j++) {
                dp[i][j] = dp[i-1][j]; 
                if (j >= w[i]) 
                dp[i][j] = dp[i][j] | dp[i][j-w[i]];// 选与不选,对应1与0,若w[i]~j都可以选,说明j也可以凑出,为1,那不能凑出的j就是0了  
            }
        }
        int ans = 0;
        for (int j = 0; j < 10000; j++)
            if (!dp[n][j]) // 不能凑出  
            ans++;
        cout<<ans<<endl;
    }
    return 0;
}

一维dp做法:

#include <iostream> 
using namespace std;

const int N = 105;
bool dp[10005];
int n;
int w[N];

int gcd(int a,int b)
{
    return b ? gcd(b,a%b) : a; // tips:第一组d = 0 传过来最大公因数就是w[i] 
}

int main()
{
    ios::sync_with_stdio(false);
    
    cin>>n;
    int d = 0;
    for (int i = 1; i <= n; i++) {
        cin>>w[i];
        d = gcd(d,w[i]); 
    }
    if (d != 1) cout<<"INF\n";
    else 
    {
        dp[0]= true;
        for (int i = 1; i <= n; i++) { // 一维完全背包模板  
            for (int j = w[i]; j < 10000; j++) {
                dp[j] |= dp[j-w[i]]; // 等同二维的解释  
            }
        }
        int ans = 0;
        for (int j = 0; j < 10000; j++) {
            if (!dp[j]) ans++;
        }
        cout<<ans<<endl;
    }
    return 0;
}

写的不够好,建议去看开头的链接\--。--/

补:回头看这篇文章把裴蜀定理看成了装逼定理【捂头笑哭】。

posted @ 2021-08-12 19:36  rainyMo  阅读(261)  评论(0)    收藏  举报