HDU 1539 Shredding Company(DFS + 路径存储) + 全排列的回顾

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的时候,联系全排列往往能使人豁然开朗

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值