包子凑数(完全背包,数论)
题干初看较晦涩,简言之:
用现有的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; }
写的不够好,建议去看开头的链接\--。--/
补:回头看这篇文章把裴蜀定理看成了装逼定理【捂头笑哭】。