搜索例题精选
邮票面值设计
[NOIP1999 提高组] 邮票面值设计
题目描述
给定一个信封,最多只允许粘贴 N N N 张邮票,计算在给定 K K K( N + K ≤ 15 N+K \le 15 N+K≤15)种邮票的情况下(假定所有的邮票数量都足够),如何设计邮票的面值,能得到最大值 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 1∼6 分之间的每一个邮资值都能得到(当然还有 8 8 8 分、 9 9 9 分和 12 12 12 分);如果面值分别为 1 1 1 分、 3 3 3 分,则在 1 ∼ 7 1\sim 7 1∼7 分之间的每一个邮资值都能得到。可以验证当 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;
}