思路分析:
- 首先这道题需要知道的性质:
- 性质1:对于两个数a,b,由a,b凑出的数
c=ax+by
满足c=k*gcd(a,b)
,这个性质可以推广到n个数; - 由1得,如果n个数的
gcd!=1
,说明在无穷个数,只有部分倍数能凑出,所以答案为INF; - 性质2:如果两个数a,b
互质
,则a,b不能凑出的最大的数为a*b-a-b
;(暂且不会证明,太菜了) - 100内,最大的两个互质的数为100和99,这两个数字不能凑出的上界为100*99-100-99,当数字更多的时候,这个
上界必然更小
(可选的数字变多了
) - 然后这是一道典型的完全背包,每个物品无穷多个,按照常规的线性Dp分析:
由上面两条性质,结合这道题的DP思路,${\color{Red}f[i][j] }$表示只考虑前i个数,能否凑出j;
状态转移:${\color{Red} f[i][j]|=f[i-1][j-a[i]*k] }$,k表示a[i]选择的次数,满足${\color{Red} j-a[i]*k>=0;}$
注意
:f[0][0]=true;
得到暴力写法:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110,M=10000;
bool f[N][M];
int a[N];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int p=a[1];
for(int i=2;i<=n;i++) p=__gcd(p,a[i]);
if(p!=1) cout<<"INF"<<"\n";
else{
f[0][0]=true;
for(int i=1;i<=n;i++){
// f[i][a[i]]=true;
for(int j=0;j<=M;j++){
for(int k=0;k*a[i]<=j;k++){
f[i][j]=f[i][j]||f[i-1][j-k*a[i]];
}
}
}
int res=0;
for(int i=0;i<=M;i++){
if(!f[n][i]) res++;
}
cout<<res<<"\n";
}
return 0;
}
优化写法:
优化:出现状态重叠
${\color{Red}:f[i][j-a[i]]}是由 {\color{red} f[i-1][j-a[i]],f[i-1][j-a[i]].....f[i-1][j-a[i]*k]}$
所以 $f[i][j]=f[i-1][j]||f[i][j-a[i]]$,同层转移,优化为一维,f[j]|=f[j-a[i]],从小到大枚举
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=110,M=10000;
bool f[M];
int a[N];
int n;
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int p=a[1];
for(int i=2;i<=n;i++) p=__gcd(p,a[i]);
if(p!=1) cout<<"INF"<<"\n";
else{
f[0]=true;
for(int i=1;i<=n;i++){
f[a[i]]=true;
for(int j=a[i];j<=M;j++){
f[j]|=f[j-a[i]];
}
}
int res=0;
for(int i=0;i<=M;i++){
if(!f[i]) res++;
}
cout<<res<<"\n";
}
return 0;
}