[蓝桥杯][2017年第八届真题]包子凑数 :题解和定理(最大公因数、可表示数的状态转移)

题目链接

本题实际上问的是:

对于方程ax + by + cz + ...... = Const方程而言(a,b,c...是系数,x,y,z...是变量,Const是常数),对于任意给定的Const,是否存在整数x,y,z...,使得方程成立。

实际上,对于这类整数不定方程,我们有以下性质:

设a,b,c...的最大公因数为t:

1.若t = 1(即这几个数互质),那么方程一定有解,且使得成立的x,y,z...有无数多个。其中,x,y,z...的解空间是整数。

2.若t = 1,x,y,z...的解空间是非负数时,方程可能无解,但是,使方程无解的Const有限,这种Const不超过max(a,b,c...)*max(a,b,c...).

3.若t > 1,则有无数多个Const,使得方程无解。

【注】n个数互质不是两两互质,而是t == 1.

 

首先先找最大公因数:

int gcd(int a,int b){//get the gcd of the num a & b
    return b==0?a:gcd(b,a%b);
}

这是一种递归写法

本题思路:背包/状态转移

一、(完全)背包做法

    本题可以通过完全背包求解的原因是,题目中说“每种蒸笼都有非常多笼,可以认为是无限笼”。那么可以用完全背包实现这个题目。设一位数组f[k] (k在0到10000)。但这个f[k]不牵扯到价值问题,表示的是 -- k这个数能否由已知的数字表示?1:0.

    那么设置f[0] = 1,因为0一定能被表示出来;其它default为0.

    对于获得最大价值的完全背包问题,代码是:   dp[ j ] += max(dp[ j-w[i] ],dp[ j ]);

    这表征的是对每个j获得最大价值,最后dp[v]就是最大价值数。

    而本题不是考虑“价值”,而是能否满足,那么仅需写作:

    dp[ j ] = max(dp[ j-w[i] ],dp[ j ]);//+= 换成 = ,因为如果是1,取完max之后就可以一直保持,表征本j可以被表示。

    实际上,为了减小运算,还可以写成这样:

for(int i = 1;i <= n;i++){
    for(int j = w[i];j <= 10000;j++){//注意j从小到大
        f[j] = max(f[j] , f[j-w[i]]);//这是存在性问题,找到一种可达方案即可。
    }
}
//注意:这里的max实际上是取0和1中的1,一旦j可以被表示出来,那么它f[j]就是1,不再改变。

这样,最终未被标记的(f[ k ] == 0)点,就是无法被表示出的点。

 

二、状态转移

    这本质上和背包是一样的,大概的思路还是设置f[ 0 ] = 1,因为0可以被表示。

    然后:

for(int i = 1;i <= n;i++){
	for(int j = 0;j <= 10000;j++){//注意j从0开始
		if(j+w[i] > 10000) continue;//越界特判 
		if(f[j]) // 如果j可以被凑出
		f[j+w[i]] = 1;//类似递归,只不过是“正向思路” 
	}
}

    这样最后没有被标记的数也是无法被表示出来的数。

 

三、AC代码

 

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
int a[10005] = {0};
int w[105] = {0};
int n;
int gcd(int a,int b){
	return b == 0?a:gcd(b,a%b);
} 

int max(int a,int b){
	return a>b?a:b;
}
int main(){
	int gcdnum = 1;
	cin >> n;
	for(int i = 1;i <= n;i++){
		cin >> w[i];
	}
	gcdnum = w[1];
	for(int i = 2;i <= n;i++){
		gcdnum = gcd(gcdnum,w[i]);
	}
	
	if(gcdnum != 1){
		printf("INF\n");
		return 0;
	}
	a[0] = 1;
	int ans = 0;
	
//	for(int i = 1;i <= n;i++){
//		for(int j = w[i];j <= 10000;j++){
//			a[j] = max(a[j],a[j-w[i]]);
//		}
//	}

	for(int i = 1;i <= n;i++){
		for(int j = 0;j <= 10000;j++){
			if(j+w[i] > 10000) continue;//越界特判 
			if(a[j]) // 如果j可以被凑出
			a[j+w[i]] = 1;//类似递归,只不过是“正向思路” 
		}
	}
	
	for(int i = 1;i <= 10000;i++){
		if(a[i] == 0) ans++;
	}
	
	printf("%d\n",ans);
	
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值