Sample Input
50 12346
376 144139
927438 927438
18 3312
9 3142
25 1299
111 33333
103 862150
6 1104
0 0
Sample Output
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
这题题目很长,看起来很复杂,实际上。。。也很复杂哈哈。其实题目虽长,但题意不难理解,看输入输出数据就能猜出了。
题目大意:给定一个数n, 和一个数m, 要求将m拆分成若干段,并且要使这若干段之和最接近于n,如果只有一种拆法,就输出这种拆法。如果对于同一个最接近的和,有多种拆法,就输出rejected。如果所有拆法得到的和都比n大,那么就输出error。
尽管题意很快弄清楚了,但写的时候也真是让我掉了层皮,最终弄了半天还是没整明白,只把dfs的边界条件写清楚了,回溯那里写的不对。看了看网上的参考后,思路明确了,就是对字符串str分割点的枚举。
分割点范围就是0–len - 1。
AC代码如下:
#include<iostream>
#include<memory.h>
using namespace std;
int t;
const int maxn = 20;
string str;
int len, flag, ans, l, rl, rsum;
int x[maxn]; //解向量:装分解的各个数字
int res[maxn];
void dfs(int pos, int sum) //xnum此时的数
{
if(sum > t)
return ;
if(pos == len) //base case
{
if(sum > ans)
{
rsum = 0;
flag = 1; //表示找到一个比ans更接近的
ans = sum; //并暂时把最接近的值存起来
for(int i = 0;i < l;i++)
{
res[i] = x[i];
rsum += res[i];
}
rl = l;
}
else if(sum == ans)
{
flag = 2;
}
return ;
}
else
{
int temp = 0;
for(int i = pos;i < len;i++) //只考虑第一层:就是对分割点的枚举
{
temp = temp * 10 + str[i] - '0';
x[l++] = temp;
dfs(i + 1, sum + temp); //不用考虑内部细节, 它内部处理完
l--;
}
}
}
//字符串转整数
int parseInt()
{
int temp = 0;
for(int i = 0;i < len;i++)
{
temp = temp * 10 + str[i] - '0';
}
return temp;
}
int main()
{
while(scanf("%d", &t) && t != 0) //这里若写whiletrue会出现OLE
{
flag = 0;
ans = 0;
l = 0;
rl = 0;
rsum = 0;
memset(x, 0, sizeof(x));
memset(res, 0, sizeof(res));
cin >> str;
len = str.length();
if(parseInt() == t) //target与纸条上的数相等
{
cout << t << " " << t << endl;
continue;
}
else
dfs(0, 0);
if(flag == 0)
cout << "error" << endl;
else if(flag == 2)
cout << "rejected" << endl;
else if(flag == 1)
{
cout << rsum;
for(int i = 0;i < rl;i++)
cout << " " << res[i];
cout << endl;
}
}
return 0;
}
刚开始整个代码让我有些不理解的地方就是回溯for循环那块
如下:
int temp = 0;
for(int i = pos;i < len;i++)
{
temp = temp * 10 + str[i] - '0';
x[l++] = temp;
dfs(i + 1, sum + temp);
l--;
}
后来想到了全排列回溯那部分的写法,受到了启发
全排列如下:
for(int i = 1;i <= n;i++)
{
if(tag[i] != 1)
{
x[k] = i;
tag[i] = 1;
dfs(k + 1);
tag[i] = 0;
}
}
其实这两块意思是很像
我们一步一步分析看看。首先,开始枚举第一位数字x[1]的所有情况,由于第一位数字有1–n这么多种情况,所以我们需要枚举,等穷举完了,问题不就解决了吗?所以用for循环。
那么针对第一位数字取1的情况,当tag[i] != 1,我们就可以取1。然后我们把第一位处理完之后,剩下的后n - 1位就用递归去遍历,那么当dfs(k + 1)这句话做完后,实际上由1开头的全排列就已经全部遍历完了!
遍历1开头的全排列之后我们要干嘛呢?对,就是要开始遍历2开头的全排列了。所以此时i也变成了2,然后同样的,做完了dfs(k + 1)这句话后,以2开头的全排列其实也遍历完了!
所以说,这里i从1—n之后,1—n的全排列就全部遍历完了,而我们分析递归时,只分析第一层就行了,不要深入到内部去,因为那样很容易弄昏自己。
好,再回到这个题的代码
int temp = 0;
for(int i = pos;i < len;i++)
{
temp = temp * 10 + str[i] - '0';
x[l++] = temp;
dfs(i + 1, sum + temp);
l--;
}
比如说我们要对字符串str = "12345"进行一个合理拆分,那么我们肯定要穷举出所有种拆分情况,这时我们就要对 拆分位置 进行一个枚举和全排列中1 – n进行枚举一样的道理。
我们从具体情况开始分析
面对字符串12345, 我们的分割点可以为0–4, 所以我们的枚举范围应该是pos到len, pos会作为参数给进来,一开始pos给进来是0。当我们i = 0时,我们把字符串首位的字符转换为整数temp存起来, 然后dfs(i + 1, temp + sum)做完之后,分割点为i = 0的所有枚举情况都已经走了一遍了。接下来再做i = 1时, 一直做到i = len - 1时,就把所有的情况都枚举完了!然后再推广到一般
通过此题确实学到了很多东西,今后分析dfs的时候,联系全排列往往能使人豁然开朗