[完全背包] NOIP2018 货币系统

[完全背包] NOIP2018 货币系统


题面

题目描述

在网友的国度中共有(n)种不同面额的货币,第(i)种货币的面额为(a[i]),你可以假设每一种货币都有无穷多张。为了方便,我们把货币种数为(n)、面额数组为(a[1..n])的货币系统记作((n,a))

在一个完善的货币系统中,每一个非负整数的金额(x)都应该可以被表示出,即对每一个非负整数(x),都存在(n)个非负整数(t[i])满足(a[i] imes t[i])的和为(x)。然而, 在网友的国度中,货币系统可能是不完善的,即可能存在金额(x)不能被该货币系统表示出。例如在货币系统(n=3,a=[2,5,9])中,金额(1,3)就无法被表示出来。

两个货币系统((n,a))((m,b))是等价的,当且仅当对于任意非负整数(x),它要么均可以被两个货币系统表出,要么不能被其中任何一个表出。

现在网友们打算简化一下货币系统。他们希望找到一个货币系统((m,b)),满足((m,b))与原来的货币系统((n,a))等价,且(m)尽可能的小。他们希望你来协助完成这个艰巨的任务:找到最小的(m)

输入输出格式

输入格式:

输入文件的第一行包含一个整数(T)表示数据的组数。

接下来按照如下格式分别给出(T)组数据。 每组数据的第一行包含一个正整数(n)。接下来一行包含(n)个由空格隔开的正整数 (a[i])

输出格式:

输出文件共有(t)行,对于每组数据,输出一行一个正整数,表示所有与((n,a))等价的货币系统((m,b))中,最小的(m)

Sample Input

2
4
3 19 10 6
5
11 29 13 19 17

Sample Output

2
5


题解

我们先证明一个结论:

那么要使(m)最小,((m,b))中的每一个元素必定存在于((n,a))

下面给出这个结论的证明:

假设((m,b))中包含一个不存在于((n,a))中的元素
则有两种情况:

  1. 这个元素可以被((n,a))中的元素表示,那么此时(m)不满足最小,因为这个元素可以被剔除
  2. 这个元素不能被((n,a))中的元素表示,那么此时不满足((n,a))((m,b))等效

所以((m,b))中不包含任何不存在于((n,a))中的元素

那么我们就可以直接从((n,a))中剔除可以被自己表示的元素就可以得到((m,b))

只需要背包一下就行了,思路还是很清晰的

上代码:

#include<bits/stdc++.h>

using namespace std;

const int MAXN=30001;

int n,ans,t,maxn=0,a[MAXN],vis[MAXN];
bool tf[MAXN],dp[MAXN];//tf[i]表示数字i是否出现在(n,a)中
                       //dp[i]表示数字i是否可以被(n,a)表示

int main(){
    scanf("%d",&t);
    while(t--){
        scanf("%d",&n);ans=n;
        memset(vis,0,sizeof(vis));
        memset(tf,0,sizeof(tf));
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++){scanf("%d",&a[i]);tf[a[i]]=true;maxn=max(maxn,a[i]);}
        sort(a+1,a+1+n);
        dp[0]=true;
        for(int i=a[1];i<=maxn;i++){
            for(int j=1;j<=vis[0];j++)if(dp[i-vis[j]])dp[i]=true;//背包
            if(dp[i]){
                    if(tf[i])ans--;//如果可以被表示,则剔除
            }else{
                if(tf[i]){vis[++vis[0]]=i;dp[i]=1;}//否则用它来表示其他的数值
            }
        }
        printf("%d
",ans);
    }
}

原文地址:https://www.cnblogs.com/2016gdgzoi316/p/9994435.html