描述
你现在负责设计一种新式的碎纸机。一般的碎纸机会把纸切成小片,变得难以阅读。而你设计的新式的碎纸机有以下的特点:
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
题目解析
这道题直接看十分复杂,不容易想出方法,我们不如边写程序边梳理思路——
首先是外层的循环。由于题目的输入中并没有给出具体的组数,我们不便于用for循环写出,于是选用while无限循环(do-while不推荐)。但是我们不能让程序就这样进行下去,好在题目的输入给出了结束条件。根据结束条件,我们在当输入为0时用break退出循环即可(也可以用return 0直接结束程序)。
再是输入。虽然题目说的是输入整数,但我们不一定要用int储存。我们知道#include
中的string(字符串)类型含有一个用于找子串的函数substr,再联系本题,我们可以把分割理解为找出全部子串,于是作者选择不用int储存将要分割的数,而是用string储存。注意string只能用cin读入。
接下来就是定义变量。具体如下:
1. 函数参数表(形参,sum、begin初值均为0,Q在主程序调用时规定,不再改变):
sum(int)-当前分割出的总和;
begin(int)-还需要分割部分的起始下标;
Q(bool)-区别当前函数 查找最大分割结果 和 查找分割方案 的两种用途
2. 全局变量:
ma(map< long,int >)-每一个分割结果出现的次数,下标为分割结果,其对应数据为出现次数;
a(int)-规定数;
ans(int)-当前最大分割结果,最后的数据为分割最大答案;
num(string)-将要被分割的数;
can(bool)-在最后查找最大分割结果的方案时标记是否找到;
vec(vector< int >)-在最后查找最大分割结果的方案时储存方案(逆序);
size(int)-表示当前数据的被分割数的长度
大家已经看到了,作者写了一个函数,并且有两个用途。
其中一个是找出所有分割方案中总和比规定数小的最大数,这也就是输出中满足条件时输出的第一个数。为了区别另一个用途,作者用Q来表示。这个用途的标记为false。先是一个判断是否分割完毕的if语句——若此时的需要分割的位置下标已经是最后一个数,则分割完毕。由于运行到此处时,两种用途有区别,作者就用Q来区分,当Q==false时,我们把ma中的当前分割出的总和出现次数+1,并且保存当前的最大结果。再用for循环来依次枚举切割的长度,从begin处开始取出子串存入zc(string),再转为int类型存入zcnum(int)。接下来进行下一次搜索。
另一个用途是在我们找到满足条件的分割结果之后,找到分割出此答案的方案。只在第一个if判断和for循环结束时与第一个用途有区别:
1. if语句中,若是Q==true(第二种方案),我们则需再判断此时的结果是否是最终的答案。若是,我们则改变can的值,倒推回去。
2. for语句中,若Q==true,则在搜索完毕后判断can是否为true(是否找到方案),若是,则存入vec中。
注意vec储存的答案是逆序。
最后我们再来看主程序的while循环。步骤很简单:
1. 将全局变量初始化;
2. 输入;
3. 调用函数;
4. 判断结果——若在第一次调用中没有找到合适的答案,则是”error”;若最大结果出现了两次及以上,则是”rejected”;若最大结果只出现了1次,则再调用函数,找到方案,逆序输出;
题外话
我们知道char数组可以用sscanf转换为int类型,但是如何将string转换为int呢?我们查找string的内部函数可以找到一个叫做c_str的函数,返回的是当前string字符串的char数组形式,此时就可以用sscanf转换为int类型了
程序样例
#include
#include
#include
#include
#include
#include
using namespace std;
int size,a,ans;
map<long,int> ma;
string num;
bool can;
vector<int> vec;
void flag(int sum,int begin,bool Q)
{
if(sum>a) return;
if(begin==size)
{
if(!Q)
{
ma[sum]++;
ans=max(ans,sum);
}
else
if(sum==ans) can=true;
return;
}
for(int i=1;i<=size-begin;i++)
{
string zc=num.substr(begin,i);
int zcnum;
sscanf(zc.c_str(),"%d",&zcnum);
flag(sum+zcnum,begin+i,Q);
if(can && Q)
{
vec.push_back(zcnum);
return;
}
}
}
int main()
{
while(true)
{
ans=-1e8;
can=false;
ma.clear();
vec.clear();
scanf("%d",&a);
cin>>num;
if(!a && num=="0") break;
size=num.length();
flag(0,0,0);
if(ans<0) printf("error\n");
else
if(ma[ans]==1)
{
printf("%d",ans);
flag(0,0,1);
for(int i=vec.size()-1;i>=0;i--)
printf(" %d",vec[i]);
printf("\n");
}
else printf("rejected\n");
}
return 0;
}