zoukankan      html  css  js  c++  java
  • Luogu 3423 [POI 2005]BAN-银行票据 (多重背包单调队列优化 + 方案打印)

    题意:

    给出 n 种纸币的面值以及数量,求最少使用多少张纸币能凑成 M 的面额。

    细节:

    好像是要输出方案,看来很是头疼啊。

    分析:

    多重背包,裸体???
    咳咳,好吧需要低调,状态就出来了: dp [ i ] 表示面额为 i 最少需要多少张纸币组成。
    转移:dp [ i ] = min ( dp [ i ] , dp [ i - w [ j ] × k ] + k ) ( k 表示当前纸币有几张,0 ≤ k ≤ num [ j ] 表示这类纸币的数量)

    好了你就完成了此题的部分分,本人蒟蒻打死都想不到背包还能优化可是它显然可以
    所以开始向满分冲刺,对于上方的转移你显然不能优化,对于一个转移竟然跟三个变量有关,嘿嘿嘿,这个转移很有心机

    我们需要进行方程的转换,不妨先来看一下是否存在重复,假设当前 num [ j ] = 2w [ j ] = 1 时:
    i = 3 时,可以从 3、2、1 进行转移
    i = 4 时,可以从 4、3、2 进行转移
    i = 5 时,可以从 5、4、3 进行转移
    顿时豁然开朗,存在重复存在优化的可能性,但是却对应了不同的价值,比如 i = 3 时从 2 进行转移的花费为 1 ,但是对于 i = 4 来说花费就变成了 2 ,所以对于当前的第 j 层来说其转移的值是不定的,所以无法进行优化。

    但如果先在转移之前每个值都减去 i 的话,就会出现这样的情况:
    i = 3 时,从 dp [ 1 ] - 1 + 3dp [ 2 ] - 2 + 3dp [ 3 ] - 3 + 3 转移
    i = 4 时,从 dp [ 2 ] - 2 + 4dp [ 3 ] - 3 + 4dp [ 4 ] - 4 + 4 转移
    不然发现对于 i 来说其都加了 i ,但前方的转移都是相同的,这样就可以开始进一步的优化了。

    不妨令 d = v [ i ],a = j / d,b = j % dj = a × d + b
    这样方程就变化成了:dp [ j ] = min ( dp [ b + k × d] - k )+ a
    所以只需要维护一个关于 dp [ b + k × d ] - k 单调递增的队列即可。

    最后我们再来考虑一下如何打印出最后的方案,其实只需要记录每个转移是从哪一个面额而来的以及它当前用了哪一种面额的纸币即可,且这只是其中的一种方案。

    代码:

    #include<bits/stdc++.h>
    #define MAXN 20005
    using namespace std;
    
    struct Node{
        int ans,W;
    }que[MAXN];
    int n, m, w[MAXN], num[MAXN], f[MAXN], d[205][MAXN];
    
    void print(int x, int y){                                       //打印方案
    	if (!x) return;
    	print(x-1, d[x][y]);
    	printf("%d ", (y-d[x][y])/w[x]);
    }
    
    int main(){
    	scanf("%d", &n);
    	for (int i=1; i<=n; i++) scanf("%d", &w[i]);
    	for (int i=1; i<=n; i++) scanf("%d", &num[i]);
    	scanf("%d", &m);
    	memset(f, 0x3f3f3f, sizeof f);
    	f[0]=0;
    	for (int i=1; i<=n; i++){
    		for (int j=0; j<w[i]; j++){
    			int head=1, tail=0;
    			for (int k=j, cnt=0; k<=m; cnt++, k+=w[i]){
    				if (tail-head==num[i]) head++;                  //维护队首
    				int Ans=f[k]-cnt;
    				while (head<=tail && Ans<que[tail].ans) --tail; //维护队尾
    				que[++tail].ans=Ans, que[tail].W=k;             //加入新的元素
    				f[k]=que[head].ans+cnt;                         //转移当前的最有状态
    				d[i][k]=que[head].W;                            //记录当前的最有转移从何而来
    			}
    		}
    	}
    	printf("%d
    ", f[m]);
    	print(n, m);                                                //打印方案
    	return 0;
    }
    

    小结:

    可以把该类问题进行一般化的处理,也就是将方程转化为 dp [ i ] = min ( dp [ i ] , dp [ i - w [ j ] × k ] + k × v [ i ] ) 同理它可以转化为以下的式子:
    假设 d = v [ i ],a = j / d,b = j % d,即 j = a × d + b
    dp [ i ][ j ] = max { dp [ i - 1 ] [ b + k × d ] - k × w[i] } + a × w[i] (a – m[i] <= k <= a)
    考虑用单调队列优化即可。Q A Q

  • 相关阅读:
    TIOBE2017榜单公布_PHP还会是世界上最好的语言吗?
    一个优秀的程序猿应该具备哪些技能?
    7月10日云栖精选夜读:看阿里云窄带高清如何支援优酷 让《楚乔传》更清晰
    如何修复Kindle频繁自动锁屏和解锁
    CentOS 7 配置nginx的service 脚本例子
    Linux系统磁盘分区(逻辑卷LVM)的扩充
    CentOS6.7配置软raid5(模拟故障增加硬盘)
    运行软件显示:缺少packet.dll文件
    《需求工程——软件建模》06
    《需求工程——软件建模》05
  • 原文地址:https://www.cnblogs.com/xiannvzuimei/p/9981651.html
Copyright © 2011-2022 走看看