题解 P2737 【[USACO4.1]麦香牛块Beef McNuggets】
cabasky
2017-07-26 22:57:38
楼下都能证明上界,然后都用类似完全背包的做法。这里我提供一种不同的思路。
题目大意为数组中取数,每个数取任意多次,问不能凑出来的最大数。
容易yy出来,**如果一个数k可以被凑出来,那么对于物品大小为x,k+x,k+2x,k+3x……都可以凑出来。**
那么我们**不妨对所有能凑出来的方案对任意一个物品x取模**,分成0到x-1一共x组。
我们现在要求出每一组方案的最小值。
**然后在每一组方案的最小值里取最大值,减去x,即为答案。**
为什么呢?
假设每一组能凑到的方案的最小值的最大值为t,由于这个t在这一组里是最小值,那么t-x一定不能凑出来。否则t就不是最小值了。
而且由于t在所有的组中是最大值,假设t=ax+b,那么ax+0,ax+1,ax+2………ax+x-1也可以被凑出来,这x个数是其他组别里的方案或者其他组别里的方案加上整数个x得到的。但是,t-x即ax+b-x不能被凑出来(因为t是最小值),而上面的ax+y中,y如果小于b,要么ax+y可以凑出,那么对答案没影响,要么ax+y减去任意个x可以被凑出,但是肯定小于t-x;y如果大于b,那么ax+y-x一定可以凑出,否者ax+y>t,不符合条件,所以也不是最优解。
表述不是很明了,不如探讨样例。
选择3作为x,
除以3余0的方案 最小值是0(什么都不选)
除以3余1的方案 最小值是10 选一个10
除以3余2的方案 最小值是20 选两个10
那0 3 6 9 12 15 18 21……都可以由第一组的0加任意个3凑出;
10 13 16 19 22 25……都可以由第二组的10加任意个3凑出;
20 23 26 29 32 35……都可以由第三组的20加任意个3凑出。
我们发现上面的20,21,22三个数连续了,也就是这个剩余系完整了,那么大于22的数一定全都可以有这三个数加3凑出来。
但是20前面有缺的,即20-3=17,20-2\*3,20-3\*3……一定凑不出来。
对于10和0也同理。
所以这些凑不出来的数里面最大的肯定就是最大的20只减去一个3,也就是17。
那么如何求出每一组的最小值呢?
这里就是本题的精华所在,也就是**最短路**模型的转化。
**我们不妨把0到x-1建立x个点,对于每一个点,我们枚举题目给出的a[i],从x到(x+a[i])%x连一条边权为a[i]的边,表示(ax+b)%x加上a[i]变成了(ax+b+a[i])%x。**
然后0的距离是0,也就是什么都不取。
从0开始跑单源最短路。
假如跑完以后出现某个点的距离减去x大于题目的上界,那么就输出0;
假如某个点到达不了,那么显然也输出0,**因为这表示这一组的方案一定是凑不出来的**。
排除以上可能后,假如最大距离是0,那么输出0,**因为这表示所有的数都可以凑出来**。
对于这个x的选择,当然是任意的,但是x的大小即为点的个数,所以x不妨选最小的那个(然而x要保证非0)。
这题因此可以用dij写,然而数据不大,所以spfa也可以0ms过掉。
复杂度上界Θ(n\*n\*min(i))
以下是本蒟蒻丑陋无比的代码
```cpp
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
using namespace std;
#define UPPER 2000000000
typedef long long LL;
LL dis[400],val[10000],a[20],mx=-1,mn=400;
int q[10000],head,tail,used[400],n,point[10000],yest[10000],last[400],total;
void adde(int f,int t,int v){
//cout<<"add"<<f<<"->"<<t<<" "<<v<<endl;
point[++total]=t;
val[total]=v;
yest[total]=last[f];
last[f]=total;
}
void spfa(int p){
memset(dis,-1,sizeof(dis));
dis[p]=0;
used[p]=1;
head=tail=1;
q[1]=p;
int f,t,v;
while(head<=tail){
f=q[head];
//cout<<"f="<<f<<endl;
for(int i=last[f];i;i=yest[i]){
t=point[i];
//cout<<f<<"->"<<t<<" "<<val[i]<<endl;
if(dis[t]==-1||dis[f]+val[i]<dis[t]){
dis[t]=dis[f]+val[i];
if(!used[t]){
used[t]=1;
q[++tail]=t;
}
}
}
used[f]=0;
head++;
}
}
int main(){
//freopen("in.txt","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
if(mn>a[i]) mn=a[i];
}
for(int i=0;i<mn;i++)
for(int j=1;j<=n;j++)
adde(i,(i+a[j])%mn,a[j]);
spfa(0);
int flag=1;
for(int i=0;i<mn;i++){
if(dis[i]==-1||dis[i]-mn>UPPER){
flag=0;
break;
}
else{
if(dis[i]>mx) mx=dis[i];
}
}
if(!flag) puts("0");
else{
if(mx-mn>=0) printf("%lld\n",mx-mn);
else puts("0");//这里我也是后来想到的,因为mn最小,那么不可能有任何方案比mn更小,除非这个mx=0,这样才能mx-mn<0
}
return 0;
}
```