1 条题解

  • 0
    @ 2026-9-16 16:34:43

    感觉这题就是一个简单的背包。题面已经够简洁的了,可以直接去看题面。

    思路

    给你 bxb_x 个硬币,每个硬币的价值为 axa_x,要凑出的数为 kk,不难发现这种价值的硬币你最多使用 min⁡(⌊kax⌋,bx)\min(\lfloor \dfrac{k}{a_x} \rfloor,b_x) 个。这题最坏情况下第 ii 种硬币选的上限为 ⌊kai⌋\lfloor \dfrac{k}{a_i} \rfloor,且 a1a_1 到 ana_n 为 1 到 nn 的任意排列。那么经过处理后最多有

    $$\lfloor \dfrac{k}{a_1} \rfloor+\dots\lfloor \dfrac{k}{a_x} \rfloor+\dots\lfloor \dfrac{k}{a_n} \rfloor=\lfloor \dfrac{k}{1} \rfloor+\dots\lfloor \dfrac{k}{x} \rfloor+\dots\lfloor \dfrac{k}{n} \rfloor\approx k\lg(k)$$

    个硬币可以选择,再加上枚举当前选到了第几个硬币的复杂度,总复杂度就是 O(nklg⁡(k))O(n k\lg(k)),吸个氧就能通过本题。

    细节

    本题毒瘤的要求出方案,于是我们要记录当前状态下选了几个硬币,最后再倒推每个硬币用了几次。

    代码

    #include<bits/stdc++.h>
    using namespace std;
    
    const int N=2e2+10,M=2e4+10,INF=1e9;
    
    int n,a[N],b[N],k,g[N][M],f[N][M],ans[N];
    //f_{i,j} 表示前 i 种硬币凑出 j 最少要用多少硬币,g_{i,j} 表示表示前 i 种硬币凑出 j 第 i 种硬币要选几个 
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    	for(int i=1;i<=n;i++) scanf("%d",&b[i]);
    	scanf("%d",&k);
    	for(int i=1;i<=n;i++){
    		b[i]=min(b[i],k/a[i]);//处理出每个硬币最多合理使用多少次
    	}
    	for(int i=0;i<N;i++) for(int j=0;j<M;j++) f[i][j]=INF;
    	for(int l=0;l<=b[1];l++){
    		f[1][l*a[1]]=l;
    		g[1][l*a[1]]=l;
    	}
    	for(int i=2;i<=n;i++){
    		for(int j=0;j<=k;j++){
    			for(int l=0;l<=b[i];l++){
    				if(l*a[i]<=j){
    					if(f[i-1][j-l*a[i]]+l<=f[i][j]){
    						f[i][j]=f[i-1][j-l*a[i]]+l;
    						g[i][j]=l;
    					}
    				}
    			}
    		}
    	}
    	printf("%d\n",f[n][k]);
    	int l=k;
    	for(int i=n;i>=1;i--){
    		ans[i]=g[i][l];
    		l-=g[i][l]*a[i];
    	}
    	for(int i=1;i<=n;i++) printf("%d ",ans[i]);
    }
    

    感觉这么做不如楼上几位大佬的,但也能通过本题了。

    • 1

    【背包:二进制压缩】硬币2[POI 2005] BAN-Bank Notes

    信息

    ID
    3186
    时间
    1000ms
    内存
    64MiB
    难度
    9
    标签
    递交数
    63
    已通过
    3
    上传者