题解 P3423 [POI2005]BAN-Bank Notes

Posted thinkofblank

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了题解 P3423 [POI2005]BAN-Bank Notes相关的知识,希望对你有一定的参考价值。

? 本题有两个问,第一个是求最少硬币数,第二个则是求方案(翻译竟然没写。。。)。

? 首先,我们来解决第一问。

? 我们可以很容易想出,这是一个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
",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
",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就过了此题

以上是关于题解 P3423 [POI2005]BAN-Bank Notes的主要内容,如果未能解决你的问题,请参考以下文章

Bzoj 1537: [POI2005]Aut- The Bus 题解 [由暴力到正解]

「题解」POI2005 AKC-Special Forces Manoeuvres

BZOJ1529 [POI2005]ska Piggy banks

BZOJ 1529 [POI2005]ska Piggy banks(并查集)

BZOJ1537: [POI2005]Aut- The Bus

[POI2005]A Journey to Mars 单调队列