Programming Challenges 习题6.6.3

本文探讨了如何解决一个特定的计数问题,即找出使用1、2、3、4的排列,使得和为给定数值n的方法总数,其中1和4视为相同。通过动态规划(DP)的方法,构建了一个高精度的解决方案,避免了直接搜索的超时问题,并详细介绍了DP递推公式和高精度加法函数的实现。

PC/UVa:110603/10198

Counting

给一个输入n,找出有多少种使用1234的排列,使得和为n。特别的,这里的14是相同的,都当做1用,比如1 + 2 + 3 = 6,而4 + 2 + 3 = 6。假如输入是4,则排列方式有1311222111112121131这么多种,又因为14都代表1,所以总的排列方式为2 + 4 + 1 + 16 + 4 + 4 + 2 = 33种。

写之前先在uDebug上试了一下输入是1000的时候结果有过大,结果很大,所以这题肯定得是高精度了。

然后就想可不可以直接计算。按照搜索的方式可以把所有可能的排列算出来,按照123扩展的方式,会得到排列1111112121132112231。在n特别大时,这一次的搜索就用去了很长时间了,然后再根据14等价的条件,进行高精度运算,超时是没得说了,所以这题应该空间换时间。

DP中有一个上台阶的题目,是说第n级台阶可以通过n - 1级上1步得到,也可以通过n - 2级一次上2步得到,也可以通过n - 3级一次上3步得到。这道题可以通过将123加入到已有排列的末尾得到新的排列,所以递推公式就是F(n) = F(n - 3) + F(n - 2) + 2 * F(n - 1)

代码里边有一个add2()函数,每次两个数相加,4个数要加3次,也就是3倍的复杂度。这么运行的话建表的时间大概8秒,肯定会超时了,然后就又写了一个add4()函数,4个相加只加1次,建表时间大概2秒。因为参数中保证了str1.size() <= str2.size() <= str3.size() == str4.size(),所以肯定是str1先退出,然后是str2

#include <iostream>
#include <string>
#include <vector>

#define N 1000

using namespace std;

string add2(const string &str1, const string &str2)
{
	size_t i = 0, j = 0;
	string strRet;
	int carry = 0, sum = 0;
	while (i < str1.size() && j < str2.size()){
		sum = str1[i] - '0' + str2[j] - '0' + carry;
		if (sum >= 10){
			sum -= 10;
			carry = 1;
		}
		else carry = 0;
		strRet.push_back(sum + '0');
		i++, j++;
	}
	while (i < str1.size()){
		sum = str1[i] - '0' + carry;
		if (sum >= 10){
			sum -= 10;
			carry = 1;
		}
		else carry = 0;
		strRet.push_back(sum + '0');
		i++;
	}
	while (j < str2.size()){
		sum = str2[j] - '0' + carry;
		if (sum >= 10){
			sum -= 10;
			carry = 1;
		}
		else carry = 0;
		strRet.push_back(sum + '0');
		j++;
	}
	if (carry == 1) strRet.push_back('1');
	return strRet;
}

string add4(const string &str1, const string &str2, const string &str3, const string &str4)
{
	size_t i1 = 0, i2 = 0, i3 = 0, i4 = 0;
	string strRet;
	int carry = 0, sum = 0;
	while (i1 < str1.size()
		&& i2 < str2.size()
		&& i3 < str3.size()
		&& i4 < str4.size()){
		sum = str1[i1] - '0' + str2[i2] - '0' + str3[i3] - '0' + str4[i4] - '0' + carry;
		carry = sum / 10; 
		sum = sum % 10;
		strRet.push_back(sum + '0');
		i1++, i2++, i3++, i4++;
	}
	while (i2 < str2.size()
		&& i3 < str3.size()
		&& i4 < str4.size()){
		sum = str2[i2] - '0' + str3[i3] - '0' + str4[i4] - '0' + carry;
		carry = sum / 10;
		sum = sum % 10;
		strRet.push_back(sum + '0');
		i2++, i3++, i4++;
	}
	while (i3 < str3.size()
		&& i4 < str4.size()){
		sum = str3[i3] - '0' + str4[i4] - '0' + carry;
		carry = sum / 10;
		sum = sum % 10;
		strRet.push_back(sum + '0');
		i3++, i4++;
	}
	while (i4 < str4.size()){
		sum = str4[i4] - '0' + carry;
		carry = sum / 10;
		sum = sum % 10;
		strRet.push_back(sum + '0');
		i4++;
	}
	if (carry > 0) strRet.push_back(carry + '0');
	return strRet;
}

void build(vector<string> &vecstrTbl)
{
	string str;
	for (size_t i = 3; i < N; i++)
	{
		str = add4(vecstrTbl[i - 3], vecstrTbl[i - 2], vecstrTbl[i - 1], vecstrTbl[i - 1]);
		/*
		str = vecstrTbl[i - 3];
		str = add2(str, vecstrTbl[i - 2]);
		str = add2(str, vecstrTbl[i - 1]);
		str = add2(str, vecstrTbl[i - 1]);
		*/
		vecstrTbl.push_back(str);
	}
}

int main()
{
	int n = 0;
	vector<string> vecstrTbl;
	vecstrTbl.push_back("2");
	vecstrTbl.push_back("5");
	vecstrTbl.push_back("31");
	build(vecstrTbl);
	while (cin >> n){
		cout << string(vecstrTbl[n - 1].rbegin(), vecstrTbl[n - 1].rend()) << endl;
	}
	return 0;
}
/*
2
3
*/

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值