搜索例题精选

邮票面值设计

邮票面值设计

[NOIP1999 提高组] 邮票面值设计

题目描述

给定一个信封,最多只允许粘贴 N N N 张邮票,计算在给定 K K K N + K ≤ 15 N+K \le 15 N+K15)种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大值 M A X MAX MAX,使在 1 1 1 M A X MAX MAX 之间的每一个邮资值都能得到。

例如, N = 3 N=3 N=3 K = 2 K=2 K=2,如果面值分别为 1 1 1 分、 4 4 4 分,则在 1 ∼ 6 1\sim 6 16 分之间的每一个邮资值都能得到(当然还有 8 8 8 分、 9 9 9 分和 12 12 12 分);如果面值分别为 1 1 1 分、 3 3 3 分,则在 1 ∼ 7 1\sim 7 17 分之间的每一个邮资值都能得到。可以验证当 N = 3 N=3 N=3 K = 2 K=2 K=2 时, 7 7 7 分就是可以得到的连续的邮资最大值,所以 M A X = 7 MAX=7 MAX=7,面值分别为 1 1 1 分、 3 3 3 分。

输入格式

2 2 2 个整数,代表 N N N K K K

输出格式

输出共 2 2 2 行。

第一行输出若干个数字,表示选择的面值,从小到大排序。

第二行,输出 MAX=S S S S 表示最大的面值。

样例 #1

样例输入 #1

3 2

样例输出 #1

1 3
MAX=7

没有K= 2 的 样例

范围从一开始必然有1

K = 3

N = 1

易得 1 2 3,MAX = 3

N = 2

2 = 1 + 1

假设一个? = 2

同样情况另一个? = 3

那么显然有距离(我们称能拼成一段的最大距离)有重叠。

当? = 3(2 = 1 + 1)

3 = 3

4 = 1 + 3

5 = ?

1 3 5

MAX = 5

同样是错的

最佳数据1 3 4,MAX = 8
这里是我的草稿(而且是错的不用看浪费时间)

我们就按上面的思路来搜索。先确立一个数组a用来存确立的数字。

1.a[1] = 1

2.(基本)单调递增

用一个数num来判断能否凑出:

用cnt记录已经确立了的数字的个数

因为我们保证了已经确立了的数是均匀分布

max =N * a[cnt],min = 1,[min,max]都能取到

if(max > =num && num >= min)num ++

else

newmax <= num && newmin +(N - 1) a[cnt] >= num

newmax = num,newmin = num - (N - 1)a[cnt];

我们取[newmin,newmax]中的所有分支进行搜索

a[++ cnt] = new

if(cnt == K)选择完毕,取MAX

此时num ++照常

不算a[cnt]之前的数可以凑出[1,num - 1],取到num - 1时已经是极限了即a[cnt - 1] + a[cnt - 2]

而a[cnt - 1] + a[cnt - 2] 中的任意一段都能取到

所以MAX = a[cnt - 1] + a[cnt - 2] + a[cnt] = num - 2 + num - 1 = 2 * num - 3

K = 3,N = 2

1 3 4 8

以此验证

num = 2

max = 2,min = 2,num ++;

num = 3;

max = 2

newmax = num = 3,newmin = num - (2 - 1)a[cnt] = 2

2 , 3;

a[++ cnt] = 2;

num = 4

max = 4,min = 1

num = 5

newmax = num=5,newmin = num - a[cnt] = 3

3,4,5

a[++cnt] = 3

num = 6 = 2 * 3

num = 7!MAX = 6

a[++ cnt] = 4

num = 6 = 2 + 4

num = 7 !num = 6

a[++ cnt] = 5

num = 6 = 1 + 5

num = 7 = 5 + 2

MAX = 7,ans = 7

回溯a[++ cnt] = 3

num = 4 = 1 + 3

num = 5(从这里开始我们判断一个数字可行不可行的方法有问题了)

2,3,4,5

a[++ nct] = 2

5 = 2 + 3

正解

其实取数的思想和错误想法差不多,具体看注释花不了多少时间。单调递增这个性质一定要维护。

难点就在于求最大连续数。

错误想法里我是用MAX和MIN,但我还没有确定这个数就说明MAX,MIN中的所有数都可以取到。

要注意利用之前的数据,确保用的少于N张邮票,这里就是利用这个条件。用DP求的

很是优美

#include<iostream>
#include<cstring>//头文件
using namespace std;
int a[17],n,k,ans[17],maxn;//a【】表示这种方法的邮票,ans【】表示如今取得的解即要输出的
int dp(int t,int mx){
    int f[50000];//f[i]为拼i所需的最少数的个数
    f[0]=0;//边界
    for(int i=1;i<=a[t]*n;i++)
      f[i]=50000;//赋初值赋一个尽可能地大就可以了
    for(int i=1;i<=t;i++)            //从第一位找到目前的位数把所有已找的邮票都枚举 
      for(int j=a[i];j<=a[t]*n;j++)   //因为不可能找到比自己小的数,所以从自己开始找 
        f[j]=min(f[j],f[j-a[i]]+1);    //比较上几次已找到的最小需要位数和即将要找的相比较,取较小值 
for(int i=1;i<=a[t]*n;i++)
      if(f[i]>n)//如果所需最小的个数大于n就意味着这种情况不符合,但f【i-1】是符合的不然f【i-1】就会判断所以不符合返回i-1
        return i-1;
    return a[t]*n;//如果到a【t】*n的f【i】都满足意味着能取到的最大连续数就是a【t】*n
}
void dfs(int t,int mx){              // 为什么全部找完:因为多几张邮票肯定比少几张邮票可能的情况多,所以全部找完是最好的  
    if(t==k+1){        //如果所有邮票数已经找完,那么就和 maxn比较谁更大   
        if(mx>maxn){
            maxn=mx;
            for(int i=1;i<=t-1;i++)
              ans[i]=a[i];} //保存所需要的邮票面值  
        return;
        }
    for(int i=a[t-1]+1;i<=mx+1;i++){  //继续找:为了避免重复,下一张邮票要比上一张邮票大,所以上边界是a[t-1]+1,同时它不能比最大连续值+1还大,不然最大连续值的下一个数就永远连不起来了 
      a[t]=i;
      int x=dp(t,mx);   //动归寻找此时的最大连续数 
      dfs(t+1,x);
    }
}
int main(){
    cin>>n>>k;
    dfs(1,0);  //先从第一张开始找,第一张前面没有数,所以所连续的最大数为 0 
    for(int i=1;i<=k;i++)//输出 注意打空格以及大写换行即可
      cout<<ans[i]<<" ";
    cout<<endl;
    cout<<"MAX="<<maxn<<endl;
    return 0;
}
 
#include<bits/stdc++.h>
using namespace std;
int N,K,t,MAX;
int a[50000],ans[50000];
int f[50000];

int dp(int t)
{
	memset(f,0x3f,sizeof f);
	
	f[0] = 0;
	
	for(int i = 1;i <= t;i ++)
	{
		for(int j = a[i];j <= a[t] * N;j ++)
		{
			f[j] = min(f[j],f[j - a[i]] + 1);
		}
	}
	
	for(int i = a[t] + 1;i <= a[t] * N;i ++)
	{
		if(f[i] > N)return i - 1;
	}
	return a[t] * N;
}

void dfs(int t,int res)
{
	if(t == K + 1){
		if(res > MAX)
		{
			MAX =  res;
			for(int i = 1;i <= t - 1;i ++)
				ans[i] = a[i];
		}
		return;
	}
	for(int i = a[t - 1] + 1;i <= res + 1;i ++)
	{
		a[t] = i;
//		printf("T=%d,i =%d\n",t,i);
		int x = dp(t);
		dfs(t + 1,x);
	}
	return;
}

int main()
{
	scanf("%d%d",&N,&K);
	
	dfs(1,0);
	
	for(int i = 1;i <= K;i ++)
		printf("%d ",ans[i]);
	printf("\nMAX=%d",MAX);	
	
	return 0;	
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值