P2725 邮票 Stamps

原题链接  https://www.luogu.com.cn/problem/P2725

题目大意

给你 m 个数,你可以从中任选不超过 n 个数(每个数可以重复选择),求最大的 k 使得 1~k 内的所有数都能被表示;

题解

70pts:

既然让求每个数能否被表示,那么我们可以顺着它的思路来设状态:

dp [ i ][ k ]:用 j 个数能否表达 k;

考虑怎么转移:

我们枚举所有的数 a [ j ],如果说 i - a [ j ] 能用 k-1 个数来表达,那么我们再加上 a [ j ] 这个数就实现了用 k 个数表达 i;

给出状态转移方程的代码:

    for(int i=1;i<=n*maxn;i++)       //maxn是最大面值再+1
    {
        for(int j=1;j<=m;j++)
        {
            if(i-a[j]<0) continue;
            for(int k=1;k<=n;k++)
            {
                dp[i][k]|=dp[i-a[j]][k-1];
            }
        }
    }

考虑到空间复杂度 O ( nm * 最大面值 ),会 MLE 的;

我们可以考虑滚动数组优化

我们看这个状态转移方程,可以发现更新 i 的时候只会用到 i - a [ j ] 的数据,而 a [ j ] 最大为 10000,所以我们可以将第一维降到 10000;

注意要随时清空之前的信息;

#include<iostream>
#include<cstdio>
using namespace std;
int read()
{
    char ch=getchar();
    int a=0,x=1;
    while(ch<‘0‘||ch>‘9‘)
    {
        if(ch==‘-‘) x=-x;
        ch=getchar();
    }
    while(ch>=‘0‘&&ch<=‘9‘)
    {
        a=(a<<1)+(a<<3)+(ch-‘0‘);
        ch=getchar();
    }
    return a*x;
}
const int N=205;
int n,m,maxn;
int a[N];
bool dp[10005][55];              //dp[i][j]:能否用j张邮票表示i
int main()
{
    n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        a[i]=read();
        maxn=max(maxn,a[i]);     //求最大面值
    }
    dp[0][0]=1;
    maxn++;                      //这里maxn要再+1,不然i与i-maxn不会同时出现的
    for(int i=1;i<=n*maxn;i++)
    {
        int can=0;               //这个数能否被表示
        for(int k=0;k<=n;k++)    //清空原有的信息
            dp[i%maxn][k]=0;
        for(int j=1;j<=m;j++)    //枚举每个数
        {
            if(i-a[j]<0) continue;
            for(int k=1;k<=n;k++)//枚举用多少个数表示i
            {
                dp[i%maxn][k]|=dp[(i-a[j])%maxn][k-1];  //状态转移方程
                can|=dp[i%maxn][k];
            }
        }
        if(can==0)               //如果当前数不能被表示,输出前一个数并结束程序
        {
            printf("%d\n",i-1);
            return 0;
        }
    }
    return 0;
}

100pts:

这 100pts 的做法就有点巧妙了。

一个显然贪心策略:

用尽量少的数来表示 i 更优。

举个例子:

假如我们可以用 3 个数,有 2 种不同的面值:1,2;

对于 3,我们可以将其表示为:3 = 1 + 1 + 1,这样的表示方法用了 3 个数;

但我们还可以这么表示:3 = 2 + 1,这样的表示方法只用了 2 个数;

如果我们采用第一种表示方法的话,我们再继续表示 4 的话就要用到 4 个数了,是不合法的;

而如果我们采用第二种表示方法的话,我们可以合法的表示了:4 = 2 + 1 + 1;

甚至有一个更优解:4 = 2 + 2;

所以说,用越少的数来表示一个数,就会留出更多的空间来表示后面的数;

那么,我们就无需去记录那些用很多数来表示的情况,只记录每个数最少能用几个数表示就好了;

状态设置

dp [ i ]:i 最少能用几个数表示;

状态转移

和上面的思路一样,只不过数组维护的东西改了而已;

dp[i]=min(dp[i],dp[i-a[j]]+1);

答案输出

如果一个数 i,最少表示 i 的数超过了 n 个,那么就说明 i 这个数无法被表示,我们输出 i-1 并结束程序;

那么,这个题就被我们转化成了完全背包问题;

Code:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int read()
{
    char ch=getchar();
    int a=0,x=1;
    while(ch<‘0‘||ch>‘9‘)
    {
        if(ch==‘-‘) x=-x;
        ch=getchar();
    }
    while(ch>=‘0‘&&ch<=‘9‘)
    {
        a=(a<<1)+(a<<3)+(ch-‘0‘);
        ch=getchar();
    }
    return a*x;
}
const int N=205;
int n,m;
int a[N];
int dp[2000005];                 //dp[i]:最少能用多少个数来表示i
int main()
{
    n=read();m=read();
    for(int i=1;i<=m;i++) a[i]=read();
    memset(dp,0x3f,sizeof(dp));  //初始化
    dp[0]=0;                     //边界条件
    for(int i=1;i<=n*10000;i++)  //枚举的最大范围
    {
        for(int j=1;j<=m;j++)
            if(i-a[j]>=0)
                dp[i]=min(dp[i],dp[i-a[j]]+1);     //状态转移方程
        if(dp[i]>n)              //i这个数无法被表示
        {
            printf("%d\n",i-1);
            return 0;
        }
    }
    return 0;
}

原文地址:https://www.cnblogs.com/xcg123/p/12150488.html

时间: 2024-10-14 04:03:30

P2725 邮票 Stamps的相关文章

洛谷P2725 邮票 Stamps

P2725 邮票 Stamps 37通过 224提交 题目提供者该用户不存在 标签USACO 难度普及/提高- 提交  讨论  题解 最新讨论 为什么RE?在codevs上AC的. 题目背景 给一组 N 枚邮票的面值集合(如,{1 分,3 分})和一个上限 K —— 表示信封上能够贴 K 张邮票.计算从 1 到 M 的最大连续可贴出的邮资. 题目描述 例如,假设有 1 分和 3 分的邮票:你最多可以贴 5 张邮票.很容易贴出 1 到 5 分的邮资(用 1 分邮票贴就行了),接下来的邮资也不难:

洛谷 P2725 邮票 Stamps Label:DP

题目背景 给一组 N 枚邮票的面值集合(如,{1 分,3 分})和一个上限 K —— 表示信封上能够贴 K 张邮票.计算从 1 到 M 的最大连续可贴出的邮资. 题目描述 例如,假设有 1 分和 3 分的邮票:你最多可以贴 5 张邮票.很容易贴出 1 到 5 分的邮资(用 1 分邮票贴就行了),接下来的邮资也不难: 6 = 3 + 3 7 = 3 + 3 + 1 8 = 3 + 3 + 1 + 1 9 = 3 + 3 + 3 10 = 3 + 3 + 3 + 1 11 = 3 + 3 + 3 +

USACO 邮票 Stamps

f[x]表示组成 x 最少需要的邮票数量 一一举例 最多贴5张邮票,有三种邮票可用,分别是1分,3分,8分 组成0分需要0张邮票 --f[0]=0 组成1分需要在0分的基础上加上一张1分邮票 --f[1]= f[0]+1 =1 (单位:张) 组成2分需要在1分的基础上加上一张1分邮票 --f[2]= f[1]+1 =2 组成3分需要 min{在2分的基础上加上一张1分邮票,在0分的基础上加上一张3分邮票} --f[3]=min{ f[2]+1 , f[0]+1 } = min{ 3 , 1} =

[usaco3.1]邮票 Stamps

题目链接 思路分析: dp[n]表示组成数字n最少需要多少张邮票 每读入一个x,我们就可以从x向2000000(最极限情况,即200张邮票和10000的面值)更新. 首先dp[n-x]必须<m 并且大于0,其次dp[n]必须>dp[n-x]+1(dp[n]=0除外),这样每次更新一次 dp[n]=dp[n-x]+1 最后从1遍历到2000000 如果中间出现断点就记录断点前一个数的值就可以了. 1 #include <cstdio> 2 int dp[2000005],m,n,an

洛谷 P2725 解题报告

P2725 邮票 Stamps 题目背景 给一组 N 枚邮票的面值集合(如,{1 分,3 分})和一个上限 K -- 表示信封上能够贴 K 张邮票.计算从 1 到 M 的最大连续可贴出的邮资. 题目描述 例如,假设有 1 分和 3 分的邮票:你最多可以贴 5 张邮票.很容易贴出 1 到 5 分的邮资(用 1 分邮票贴就行了),接下来的邮资也不难: 6 = 3 + 3 7 = 3 + 3 + 1 8 = 3 + 3 + 1 + 1 9 = 3 + 3 + 3 10 = 3 + 3 + 3 + 1

usaco Stamps

问有K种邮票,限制所有邮票只能使用N个,问从1到X连续能拼出的总值时,x是多少. 好吧,题意叙述的有点不太清楚,原题比较好理解. 下面给代码,dp[i]表示凑成总值为i时,最少使用几张. 简单背包题目. /* ID: modengd1 PROG: stamps LANG: C++ */ #include <iostream> #include <stdio.h> #include <memory.h> #define INF 2139062143 using names

uva 165 Stamps (回溯)

uva 165 Stamps The government of Nova Mareterrania requires that various legal documents have stamps attached to them so that the government can derive revenue from them. In terms of recent legislation, each class of document is limited in the number

【USACO 3.1.6】邮票

[描述] 已知一个N枚邮票的面值集合(如,{1分,3分})和一个上限K ——表示信封上能够贴K张邮票.计算从1到M的最大连续可贴出的邮资. 例如,假设有1分和3分的邮票:你最多可以贴5张邮票.很容易贴出1到5分的邮资(用1分邮票贴就行了),接下来的邮资也不难: 6 = 3 + 3 7 = 3 + 3 + 1 8 = 3 + 3 + 1 + 1 9 = 3 + 3 + 3 10 = 3 + 3 + 3 + 1 11 = 3 + 3 + 3 + 1 + 1 12 = 3 + 3 + 3 + 3 13

【DFS】STAMPS

[Poj1010]STAMPS Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 18867   Accepted: 5469 Description Have you done any Philately lately? You have been hired by the Ruritanian Postal Service (RPS) to design their new postage software. The s