openjudge_2.5基本算法之搜索_1805:碎纸机

题目

1805:碎纸机
查看提交统计提问
总时间限制: 1000ms 内存限制: 65536kB
描述
你现在负责设计一种新式的碎纸机。一般的碎纸机会把纸切成小片,变得难以阅读。而你设计的新式的碎纸机有以下的特点:

1.每次切割之前,先要给定碎纸机一个目标数,而且在每张被送入碎纸机的纸片上也需要包含一个数。
2.碎纸机切出的每个纸片上都包括一个数。
3.要求切出的每个纸片上的数的和要不大于目标数而且与目标数最接近。

举一个例子,如下图,假设目标数是50,输入纸片上的数是12346。碎纸机会把纸片切成4块,分别包含1,2,34和6。这样这些数的和是43 (= 1 + 2 + 34 + 6),这是所有的分割方式中,不超过50,而又最接近50的分割方式。又比如,分割成1,23,4和6是不正确的,因为这样的总和是34 (= 1 + 23 + 4 + 6),比刚才得到的结果43小。分割成12,34和6也是不正确的,因为这时的总和是52 (= 12 + 34 + 6),超过了50。
在这里插入图片描述
还有三个特别的规则:
1.如果目标数和输入纸片上的数相同,那么纸片不进行切割。
2.如果不论怎样切割,分割得到的纸片上数的和都大于目标数,那么打印机显示错误信息。
3.如果有多种不同的切割方式可以得到相同的最优结果。那么打印机显示拒绝服务信息。比如,如果目标数是15,输入纸片上的数是111,那么有两种不同的方式可以得到最优解,分别是切割成1和11或者切割成11和1,在这种情况下,打印机会显示拒绝服务信息。

为了设计这样的一个碎纸机,你需要先写一个简单的程序模拟这个打印机的工作。给定两个数,第一个是目标数,第二个是输入纸片上的数,你需要给出碎纸机对纸片的分割方式。
输入
输入包括多组数据,每一组包括一行。每行上包括两个正整数,分别表示目标数和输入纸片上的数。已知输入保证:两个数都不会以0开头,而且两个数至多都只包含6个数字。
输入的最后一行包括两个0,这行表示输入的结束。
输出
对每一组输入数据,输出相应的输出。有三种不同的输出结果:
sum part1 part2 …
rejected
error
第一种结果表示:
1.每一个partj是切割得到的纸片上的一个数。partj的顺序和输入纸片上原始数中数字出现的次序一致。
2.sum是切割得到的纸片上的数的和,也就是说:sum = part1 + part2 +…
第一种结果中相邻的两个数之间用一个空格隔开。
如果不论怎样切割,分割得到的纸片上数的和都大于目标数,那么打印“error”。
如果有多种不同的切割方式可以得到相同的最优结果,那么打印“rejected”。
样例输入
50 12346
376 144139
927438 927438
18 3312
9 3142
25 1299
111 33333
103 862150
6 1104
0 0
样例输出
43 1 2 34 6
283 144 139
927438 927438
18 3 3 12
error
21 1 2 9 9
rejected
103 86 2 15 0
rejected

枚举理解

目标数50,纸片有数12346。可以枚举,从二进制10000(16)到11111(31)逐个试,直到找到和是小于等于50,却也是最大的。这样切分成11101,对应二进制1是个新截取数,0属于前面,所以切分成1,2,34和6,相加的和是43满足条件最大的。
12346
11101
比对了一万组数据没错,但是wrong。请有兴趣的同学帮忙找错误。

枚举代码(比对都对,可惜wrong)

#include <bits/stdc++.h>
using namespace std;
int t,x,//目标数和现有数
d[6];//现有数放进数组中
bool k[32][6];//0到32的对应二进制,0表示不用,1表示用,就是枚举
struct num{
queue q;//每一组拆分成哪些数
};
int main(){
freopen(“data.cpp”,“r”,stdin);
//freopen(“data1.txt”,“w”,stdout);
for(int i=0;i<=63;i++){//生成6位111111对应二进制
int d=i;
int j=5;
while(d){
k[i][j–]=d%2;
d/=2;
}
//cout<<i<<“\t”;for(int j=0;j<6;j++)cout<<k[i-32][j];cout<<endl;
}
while(cin>>t>>x&&t){//多组测试数据 ,不会以零开头,会不会是零
int lhe=0,//现有数拆成最小单元的和就是最小和
he,//从最小值中找最大和
now;//当前拆分后和
vector v; //有两个以上一样的最大和就是错误
memset(d,0,sizeof(d));//现有数转成数组清空
int xi=5;//从个位开始放
while(x){//整数放进数组中
d[xi–]=x%10;
x/=10;
}
int m=-1,//找最大位
r=0,l=0;
for(int i=0;i<6;i++){
lhe+=d[i];//最小和
if(m==-1&&d[i]>0)m=i;//找到最高位
if(m>-1){r+=pow(2,6-i-1);}//找到对应最大二进制
}
l=pow(2,6-m-1);//对应最小二进制
if(t<lhe){cout<<“error\n”;continue;}//如何最小和还比目标数大,就是error
num zu;//其中一组拆分后所得数组
for(int i=l;i<=r;i++){//遍历从对应最小二进制到最大二进制
while(!zu.q.empty())zu.q.pop();//清空队列
he=0;now=0;//拆分数的和,还有拆分数
for(int j=0;j<6;j++){
if(k[i][j]){
he+=now;//拆分数加到和
if(now>0)zu.q.push(now);//如果有拆分数就放进队列
now=0+d[j];//开始新的拆分数
}else now=now*10+d[j];//增加拆分数
}
he+=now;zu.q.push(now);//把最后一组拆分数加到和里
if(lhe<he&&he<=t){lhe=he;v.clear();v.push_back(zu);}//如果是比最小数大,又比目标数小,就可以用
else if(lhe==he){v.push_back(zu);}//相等就说明多了一组答案。
//while(!zu.q.empty()){cout<<zu.q.front()<<" “;zu.q.pop();}cout<<endl;
}
if(v.size()>1)cout<<“rejected\n”;
else{
cout<<lhe<<” “;
while(!v[0].q.empty()){
cout<<v[0].q.front()<<” ";v[0].q.pop();
}
cout<<endl;
}
}
return 0;
}

正确代码(深搜枚举)

#include <bits/stdc++.h>
using namespace std;
int num,//找小于等于num的最大和
sl,//纸片(多个数字字符)中字符个数
dn,//从纸片中截取的数字个数
dx[6],//暂存从纸片中截取的各个数字
d[6],//从纸片中截取的符合条件的各个数字
sum,//从制片中截取的各个数字的最大的和,且小于等于num
k;//结果标识,0是没有答案,1是刚好,大于1就是可以切割成不同的各个数字
string s;//输入的纸片(有多个数字字符)
void qhe(int x,int n,int he){
//从纸片字符的x位置开始截取第n个部分,并加到总和里
if(xsl&&he<=num&&he>=sum){//递归出口,纸片没有字符了,截取数字和小于等于目标又大于等于
if(he
sum)k++; //两个符合条件的结果
else{//一个符合条件的结果
k=1;//标识ok
sum=he;//符合条件的纸片截取数字和
dn=n;//纸片被切分成几块
for(int i=0;i<dn;i++){//记住截取的各个数
d[i]=dx[i];//存在最终数组内
}
}
}
int dd=0;//新截取的数
for(int i=x;i<sl;i++){//从纸片各个位置开始截取
dd=dd*10+s[i]-‘0’;//纸片当前位置数加到截取数中
dx[n]=dd;//截取纸片字符放在对应数组位置
if(he+dd<=num)qhe(i+1,n+1,he+dd);//剪枝,如果加上这个截取数超过目标数就不再递归了
//该次截取一个,两个,三个,逐个试
}
}
int main(){
//freopen(“data.cpp”,“r”,stdin);
while(cin>>num>>s&&num){
sl=s.length();//纸片字符的长度
memset(d,0,sizeof(d));//清空上组截取的各个数字
k=dn=sum=0;//清除上组数据
qhe(0,0,0); //深搜,从哪个位置开始,截取第几个数字,和是多少
if(k<1)cout<<“error\n”; //0就是没有结果
else if(k>1)cout<<“rejected\n”; //大于1就是有多组结果
else{//否则就是等于1,刚好
cout<<sum<<" “;
for(int i=0;i<dn;i++)cout<<d[i]<<” ";cout<<endl;
}
}
return 0;
}

理解

  1. 相对于二进制枚举,代码量小,精巧。需要理解一遍递归过程。
  2. 要得到小于等于目标数,又最大的和,还要记住最大和对应的各个截取数,更要标记结果是有没有,重复没——不是唯一答案,不需返回。
  3. 没有交叉递归,不需要记忆化。
  4. 当和超过目标数时不需要进一步递归,这是剪枝。
  5. 递归先要明确递归出口。就是符合条件的和
  6. 从纸片每个字符开始一次截一个字符,两个,三个,四个,五个,六个,都加到和里,比对结果。递归图就是分叉树。这个递归全排列有点像,只是全排列要回溯。
  • 9
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值