本题有两个问,第一个是求最少硬币数,第二个则是求方案(翻译竟然没写。。。)。
首先,我们来解决第一问。
我们可以很容易想出,这是一个dp,我们设dp[i]表示凑出面值i最少需要多少个硬币,然后打个多重背包就好了。。。于是你就T了。。。
对于多重背包,我们通常使用一种手段:二进制拆分(听说还可以使用单调队列优化,但是本弱鸡并不会。。。QwQ)
二进制拆分,它的原理是:任何一个数都可以用2^0^,2^1^,2^2^...2^k^表示(每个最多取1个)。
于是,对于一个物品的个数C,我们把它成:C=2^0+2^1+2^2...+2^k+X(X<2^(k+1)),这我们就能表示0-C的所有数,然后,我们跑一遍01背包就好了,对于一个物品,它拆分出的物品中,被选的物品所代表的个数之和即是这个物品被选了几个。
复杂度O(nklog(n)),于是,我们成功切掉了第一问。。。
那么,重要的是第二问了。。。
某dalao曰:最优美的算法是用的暴力还能A题。
于是,我们就考虑使用dfs来解决此题
首先,我们把问题转化成了01背包,那么我们我们就需要知道:我们选了哪些数,我们怎么解决呢?
首先我们设每个物品的参数:B[i]->面值,C[i]->对答案的影响值(其实就是二进制拆分时拆出来的物品个数),D[i]->由哪个物品拆出来的。
我们假设,现在我们要知道面值x可以由哪些物品组合出来,那么,我们知道,要选择物品i的话,其必要条件是:x>=B[i]并且dp[x-B[i]]+C[i]==dp[x]。于是我们就可以用dfs来枚举选哪些物品了。
代码:
//#pragma GCC optimize()//手动Ox优化 #include<bits/stdc++.h> using namespace std; const int N=2e6+1,M=2600001; int dp[N],b[N],c[N]; int B[M],C[M],D[M]; int ti[N]; bool vis[N]; int e;int n; inline void dfs(int x){ if(!x){ for(int i=1;i<=n;++i){ printf("%d ",ti[i]); } exit(0);//找到一种方案后,输出,然后直接强制结束程序 } for(int i=1;i<=e;++i){ if(vis[i]||B[i]>x){//如果被选过了,或者超出了,则不选 continue; } if(dp[x]==dp[x-B[i]]+C[i]){//如果可以选 vis[i]=1; ti[D[i]]+=C[i];//第D[i]个选上C[i]个 dfs(x-B[i]); ti[D[i]]-=C[i]; vis[i]=0; } } } int main(){ scanf("%d",&n); for(int i=1;i<=n;++i){ scanf("%d",&b[i]); } for(int i=1;i<=n;++i){ scanf("%d",&c[i]); for(int k=0;k<=13;++k){//二进制拆分 if(c[i]>=(1<<k)){ B[++e]=(1<<k)*b[i]; C[e]=(1<<k); D[e]=i; c[i]-=(1<<k); }else{ break; } } if(c[i]){//处理剩余的 B[++e]=c[i]*b[i]; C[e]=c[i]; D[e]=i; } } memset(dp,0x3f3f,sizeof(dp));//初始化 dp[0]=0; int v; scanf("%d",&v); for(int i=1;i<=e;++i){ for(int j=v;j>=B[i];--j){ dp[j]=min(dp[j],dp[j-B[i]]+C[i]); } } printf("%d\n",dp[v]); dfs(v); return 0; }
这个代码吸氧可过,不吸氧的话,不清楚,大家可以试验下。。。
不过,这明显不是我们希望的效果。
那么,我们可以考虑优化,怎么优化呢?
我提出一种思路:
我们把所有满足:dp[v-B[i]]+C[i]==dp[v]的物品叫做v的可行物品。
我们想下,为什么不是所有可行物品可以被选择。
理由是:对于物品i,他会对dp[v],与dp[v-B[i]]的结果同时造成影响。也即是说,有可能v-B[i]的可行物品中有i,v中又有i,那么i就被选了两次!
那么,我们就可以推出:必选物品的条件:
一个物品必定被选,就相当于,该物品是可行物品且选两个该物品后就会错误或者无法选两个,即:
v>=B[i]并且dp[v]==dp[v-B[i]]+C[i],且:
v>=2B[i],dp[v-2B[i]]+2C[i]!=dp[v]或者v<2B[i]
那么,我们把所有必选的物品选了,再去dfs,就会快上许多,当然,我们可以重复这个过程直到没有关于现在体积的必选物品为止。
代码:
//pragma GCC optimize()//手动Ox优化 #include<bits/stdc++.h> using namespace std; const int N=2e6+1,M=2600001; struct node{ int B,C,D; }t[N]; int dp[N],b[N],c[N]; int B[M],C[M],D[M]; int ti[N]; bool vis[N],vit[N]; int e,n,cnt; inline void dfs(int x){ if(!x){ for(int i=1;i<=n;++i){ printf("%d ",ti[i]); } exit(0); } for(int i=1;i<=cnt;++i){ if(!vit[i]&&dp[x-t[i].B]+t[i].C==dp[x]){ vit[i]=1; ti[t[i].D]+=t[i].C; dfs(x-t[i].B); ti[t[i].D]-=t[i].C; vit[i]=0; } } } int main(){ scanf("%d",&n); for(int i=1;i<=n;++i){ scanf("%d",&b[i]); } for(int i=1;i<=n;++i){ scanf("%d",&c[i]); for(int k=0;k<=13;++k){ if(c[i]>=(1<<k)){ B[++e]=(1<<k)*b[i]; C[e]=(1<<k); D[e]=i; c[i]-=(1<<k); }else{ break; } } if(c[i]){ B[++e]=c[i]*b[i]; C[e]=c[i]; D[e]=i; } } memset(dp,0x3f3f,sizeof(dp)); dp[0]=0; int v; scanf("%d",&v); for(int i=1;i<=e;++i){ for(int j=v;j>=B[i];--j){ if(dp[j-B[i]]+C[i]<dp[j]){ dp[j]=dp[j-B[i]]+C[i]; } } } printf("%d\n",dp[v]); while(true){ bool flag=0; for(int i=1;i<=e;++i){ if(vis[i]){ continue; } if((v>=2*B[i]&&dp[v-B[i]-B[i]]+C[i]+C[i]!=dp[v]&&dp[v-B[i]]+C[i]==dp[v])||(v<2*B[i]&&v>=B[i]&&dp[v-B[i]]+C[i]==dp[v])){//必选 v-=B[i]; ti[D[i]]+=C[i]; flag=1; vis[i]=1; } } if(!flag){//如果没有必选物品了,就退出 break; } } for(int i=1;i<=e;++i){ if(vis[i]){ continue; } if(v>=B[i]&&dp[v-B[i]]+C[i]==dp[v]){//统计所有的可行物品 t[++cnt]={B[i],C[i],D[i]}; } } dfs(v);//dfs return 0; }
加了此优化后,速度飞起,仅用257ms就过了此题