• 题解 P3423 【[POI2005]BAN-Bank Notes】


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

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

    ​ 我们可以很容易想出,这是一个dp,我们设dp[i]表示凑出面值i最少需要多少个硬币,然后打个多重背包就好了。。。于是你就T了。。。

    ​ 对于多重背包,我们通常使用一种手段:二进制拆分(听说还可以使用单调队列优化,但是本弱鸡并不会。。。QwQ)

    ​ 二进制拆分,它的原理是:任何一个数都可以用20,21,22...2k表示(每个最多取1个)

    ​ 于是,对于一个物品的个数C,我们把它成:C=20+21+22...+2k+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就过了此题

  • 相关阅读:
    小程序排错(redis导致)
    linux系统删除-开头文件或目录
    nginx 反向代理
    memcache
    一次故障过程
    json.Decoder vs json.Unmarshal
    ssl中间证书
    作业10:String类
    Angular6如何引入jQuery-knob
    OpenJdk源码下载
  • 原文地址:https://www.cnblogs.com/ThinkofBlank/p/10297150.html
Copyright © 2020-2023  润新知