USACO4.1.1--Beef McNuggets
chunlvxiong的博客
题目描述:
有N种包装盒(1≤N≤10),每种包装盒可以包装牛块i块(1≤i≤256),每种包装盒可以使用任意次。求无法包装的最大的牛块数,若所有数目的牛块都能被包装,或者无法包装的最大牛快数为oo,那么输出0。
思考&分析:
其实这是个数论问题--给定N个数,求不能由这些数通过加减得到的最大数。
如果只有两个数,那么这个问题比较经典,也存在如下结论:若gcd(p,q)==1 ,则px+qy不能表示的最大数为pq-p-q。
答案最大只有256^2-256-256,那么使用DP解决即可:用dp[i][j]表示前i个数能否表示j,方程如下:
dp[i][j]=dp[i][j] or dp[i-1][j-k*num[i]](k>=0 && j>=k*num[i])
这里利用完全背包的思想进行优化,可以把方程改为:
dp[j]=dp[j] or dp[j-num[i]](j>=num[i],同时继承时从小到大继承)
那么怎么判断最大值为oo呢?只要稍微开大一些空间(例如:256^2),如果答案大于256^2-256-256,那么说明最大值为oo。
这样时间复杂度O(N*256^2),空间复杂度O(256^2)。
(注:其实这个算法可能存在小漏洞,如果N个数全部不互质,例如:6 10 15的答案是29,这样不满足上述结论,但是好像也找不到超过p*q-p-q的反例。这个算法是可以A掉此题的。)
其实我更想谈的是另一种想法:
设这N个数的最小值为T,则如果X能被组成的话,X+KT也能被组成(K>=0),所以你可以认为要求%T=X的最小的数。
这是一个最短路问题:把%T==0/1/2……T-1看成T个点,每个点都会连出N条边(X-->(X+A[i])%T,权值为A[i]),共N*T条边,并不多,可以跑SPFA解决。
接下来对于结果(dis数组)有以下几种情况:
dis[i]==i表示%T==i的所有数都能被组成-->如果所有的dis[i]==i表示所有数都能被组成,输出0。
dis[i]==oo表示%T==i的所有数都不能被组成-->表示最大不能被组成的数是oo,输出0。
dis[i]=X表示%T==i的最小能被组成的数是X-->%T==i的最大不能被组成的数是X-T,取MAX即可。
这样就可以得到结果了,如果用SPFA写时间复杂度O(T*N*T)(远不会到这个上限),用dijkstra写时间复杂度O(T*T),加堆优化可以达到O((T+N*T)log(N*T)),可以解决规模更大的问题,而且正确性更好。
贴代码:
数论+DP:
#include<bits/stdc++.h> using namespace std; const int Max=256*256; int n,a[15],dp[Max+5]; int main(){ freopen("nuggets.in","r",stdin); freopen("nuggets.out","w",stdout); scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&a[i]); memset(dp,0,sizeof(dp)); dp[0]=1; for (int i=1;i<=n;i++) for (int j=a[i];j<=Max;j++) dp[j]|=dp[j-a[i]]; int ans=0; for (int i=Max;i>=1;i--) if (!dp[i]){ ans=i; break; } if (ans<=Max-256*2) printf("%d\n",ans); else puts("0"); return 0; }
最短路:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const ll oo=1e13; const int maxn=305; const int maxm=3005; int head[maxn],Q[maxn],tot; ll dis[maxn]; int T,n,a[15],front,rear; bool vis[maxn]; struct E{ int to,len,next; }edge[maxm]; void init(){ memset(head,0,sizeof(head)),tot=0; } void makedge(int u,int v,int t){ edge[++tot].to=v; edge[tot].len=t; edge[tot].next=head[u]; head[u]=tot; } void push(int x){ vis[x]=1; Q[rear]=x; rear=(rear+1)%maxn; } void spfa(){ int u,v; memset(dis,1,sizeof(dis)); memset(vis,0,sizeof(vis)); front=rear=dis[0]=0,push(0); while (front!=rear){ u=Q[front]; for (int i=head[u];i;i=edge[i].next){ v=edge[i].to; if (dis[u]+edge[i].len<dis[v]){ dis[v]=dis[u]+edge[i].len; if (!vis[v]) push(v); } } front=(front+1)%maxn; } } int main(){ freopen("nuggets.in","r",stdin); freopen("nuggets.out","w",stdout); scanf("%d",&n); T=1<<30; for (int i=1;i<=n;i++) scanf("%d",&a[i]),T=min(T,a[i]); for (int i=1;i<=n;i++) for (int j=0;j<T;j++) makedge(j,(j+a[i])%T,a[i]); spfa(); ll ans=0; for (int i=0;i<T;i++) if (dis[i]>i) ans=max(ans,dis[i]-T); if (ans<oo) printf("%lld\n",ans); else puts("0"); return 0; }