[NOIP2018提高组] 货币系统 题解

题目链接

题目模型

给出具有n个数字的集合,求最多能删除多少个数,使得删除后的集合和原集合等价,等价的定义为:对于任意非负整数,原集合能表示则新集合也能表示出。每个数字可以使用无数次。

思路

进一步分析,哪些数字是能删除的?
如果这个数字能够通过原集合中的其它数字组合出,则这个数字可以删除。

这其实是一个完全背包问题。

将集合内的数字升序排序,推一遍完全背包,在推的过程中,判断a[i]是否能被前i个数组合出来。(组合方式是求和,且已升序排序,推到这里组合不出来,后面也不会组合出来了)。

定义状态 d p [ i ] [ j ] dp[i][j] dp[i][j] 为从前 i i i个数字,背包容量为j的情况下,能组合出的最大和

状态转移方程为: d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j − a [ i ] ∗ k ] + k ∗ a [ i ] ) dp[i][j] = max(dp[i][j], dp[i-1][j-a[i]*k] + k*a[i]) dp[i][j]=max(dp[i][j],dp[i1][ja[i]k]+ka[i])

如果 d p [ i ] [ a [ i ] ] = d p [ i − 1 ] [ a [ i ] ] dp[i][a[i]]=dp[i-1][a[i]] dp[i][a[i]]=dp[i1][a[i]],则说明前i-1个数字可以组合出 a [ i ] a[i] a[i]

显然,状态转移方程可优化为:
d p [ j ] = m a x ( d p [ j ] , d p [ j − a [ i ] ] + a [ i ] ) dp[j] = max(dp[j], dp[j-a[i]] + a[i]) dp[j]=max(dp[j],dp[ja[i]]+a[i]) j > = a [ i ] j>=a[i] j>=a[i]
完全背包具体优化过程
特别地,当 j = a [ i ] j =a[i] j=a[i]时,在转移之前,如果 d p [ j ] = j dp[j]=j dp[j]=j ,则 a [ i ] a[i] a[i]可删。

代码

#include <bits/stdc++.h>
using namespace std;
const int maxn = 100+10;
const int maxm = 25000+10;
int n,m, a[maxn];
int dp[maxm]; 
inline void init(){
    memset(dp,0,sizeof(dp));
}
inline int cal(){
    int ans = 0;
    m = a[n];
    for(int i = 1;i<=n;i++){
        int j = a[i];
        if(dp[j] != j){ 
            dp[j] = j;
            ans++;
        }
        for(j++;j<=m;j++){
            dp[j] = max(dp[j-a[i]]+a[i],dp[j]);
        }
    }
    return ans;
}
int main(){
    int t;scanf("%d",&t);
    while(t--){
        scanf("%d",&n);
        for(int i = 1;i<=n;i++){
            scanf("%d",&a[i]);
        }
        sort(a+1,a+n+1);
        init();
        printf("%d\n",cal());
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值