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;
}

 

posted @ 2017-08-24 22:19  chunlvxiong  阅读(187)  评论(0编辑  收藏  举报