您的位置:首页 > 编程语言 > C语言/C++

google竞赛题SecretSum的另一种C++解法, 使用递归生成代替循环 -- 1

2009-07-04 11:07 876 查看
这个程序最早是在2007年在CSDN上Ackarlix大虾的blog上看到的, http://blog.csdn.net/Ackarlix/archive/2007/08/30/1764739.aspx

当时自己也写了一个用递归算法的程序,但没有贴出来(本人比较懒, 习惯潜水,呵呵). 最近整理以前做过的一些东西和一些代码, 把它又做了一些改进, 例如, 原先只支持等长的模式,例如 AAAABBBBCCCC+BBBBCCCCDDDD=CCCCDDDDEEEE等, 修改后可以支持变长的模式(如 A+BC=CDE)以及带限定性的模式(如 A+BC=CD2, X+156=157, 等).

先来看看问题定义吧.

  SecretSum 是本次 google 竞赛中第二轮淘汰赛的一道分值为 500 分竞赛题。事实上,这道题目反而比同轮比赛中的那道 1000 分值的RecurringNumbers 难(RecurringNumbers 的难度水准充其量不过是道初一学生奥数竞赛题)。好了,闲话少叙,来看 SecretSum 的题目吧:

一、竞赛题目

Problem Statement

We can substitute each digit of a number with a unique letter from ''A'' to ''Z''. This way we can write groups of numbers in a single representation. For example "ABA" can represent any of the following: 101, 151, 343, 767, 929. However, "ABA" cannot mean 555 because each letter must stand for a distinct digit. Furthermore, numbers cannot begin with 0 and thus ''A'' cannot be replaced by 0. Given two such representations num1 and num2 and the result of their summation return the total number of possible combinations of numbers for which the equation holds. If no combinations are possible then return 0.

Definition

Class: SecretSum
Method: countPossible
Parameters: String, String, String
Returns: int
Method signature: int countPossible(String num1, String num2, String result)
(be sure your method is public)

Constraints
- num1, num2, and result will each contain exactly 3 uppercase letters (''A'' - ''Z'').

Examples

0)

"AAA"
"BBB"
"CCC"
Returns: 32

1)

"ABB"
"DEE"
"TTT"
Returns: 112

2)

"ABC"
"ABA"
"ACC"
Returns: 0
Leading zeroes are not allowed.

3)

"AAA"
"CDD"
"BAA"
Returns: 32

4)

"TEF"
"FET"
"AAA"
Returns: 12

5)

"ABC"
"ABC"
"BCE"
Returns: 5
We can have the following 5 sums:
124 + 124 = 248
125 + 125 = 250
249 + 249 = 498
374 + 374 = 748
375 + 375 = 750

6)

"AAA"
"AAA"
"BBB"
Returns: 4
We can have the following 4 sums:
111 + 111 = 222
222 + 222 = 444
333 + 333 = 666
444 + 444 = 888

This problem statement is the exclusive and proprietary property of TopCoder, Inc. Any unauthorized use or reproduction of this information without the prior written consent of TopCoder, Inc. is strictly prohibited. (c)2003, TopCoder, Inc. All rights reserved.

  题目的意思大致是这样的:数字0-9可以分别用A-Z中不相同的字母替代,这样就可以用字母组成的字符串代表一个数了。例如"ABA"可以代表: 101, 151, 343, 767, 929。但"ABA"不可以代表555,因为不同的字母必须取不同的数字。此外每个作为开头的字母不取0值。编写一个类 SecretSum,该类中有一个原型为 int countPossible(string num1, string num2, string result)的函数。num1、num2 和 result 就是上面所描述的类型的字符串,其中 result 代表的数为 num1 和 num2 代表的数的和,并且这几个字符串都只含有3个A-Z的字母。返回值为可能取得的组合的总数。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Ackarlix/archive/2007/08/30/1764739.aspx

下面算法的主要特点是使用了递归来生成符合模式(规则)的数, 滤掉不符合规则的数. 这个函数就是根据规则生成操作数的, 这里的参数 Status curr_state是一个std::map<char, char>, 用于传递规则中的字母的当前的映射状态, 而pattern则是输入的规则了(如abc, e3fg, etc.). 其中另一个用到的数据结构是 std::set leading_char; 用来记录打头的字母(因为打头不能为0). 另一个用到的支持函数是find_available_choices, 用来返回在当前状态下可用的数字.

vector<string> gen_num(Status curr_state, const string& pattern)
{
vector<string> result;

if(pattern.length()==1)
{
if(pattern[0]>='0' && pattern[0]<='9') //pattern is a number, just use it
result.push_back(string(1, pattern[0]));
else if(curr_state[pattern[0]] < '0')
{
if(leading_chars.find(pattern[0])!= leading_chars.end()) //a leading char, range from '1'-'9'
{
vector<char> availables = find_available_choices(curr_state, '1', '9');
for(int i=0; i<availables.size(); ++i)
result.push_back(string(1, availables[i]));
}
else //not a leading char, can be '0'-'9'(if it's unique)
{
vector<char> availables = find_available_choices(curr_state, '0', '9');
for(int i=0; i<availables.size(); ++i)
result.push_back(string(1,availables[i]));
}
}
else
result.push_back(string(1, curr_state[pattern[0]]));

return result;
}

vector<char> available_choices;
if(pattern[0]>='0' && pattern[0]<='9') //pattern is a number, just use it
available_choices.push_back(pattern[0]);
else if(curr_state[pattern[0]] < '0') //it's empty, could be assigned any legal value
{
if(leading_chars.find(pattern[0])!= leading_chars.end()) //a leading char, ranging from '1'-'9'
{
vector<char> availables = find_available_choices(curr_state, '1', '9');
for(int i=0; i<availables.size(); ++i)
available_choices.push_back(availables[i]);
}
else //not a leading char, can be '0'-'9'
{
vector<char> availables = find_available_choices(curr_state, '0', '9');
for(int i=0; i<availables.size(); ++i)
available_choices.push_back(availables[i]);
}
}
else //the value is already set, the only choice
available_choices.push_back(curr_state[pattern[0]]);

//prepare to start the loop(and recursion), find the sub patterns!
string sub_pattern = pattern.substr(1, pattern.length()-1);

for(int idx=0; idx<available_choices.size(); ++idx)
{
string leading(1, available_choices[idx]);
curr_state[pattern[0]] = available_choices[idx];

vector<string> temp = gen_num(curr_state, sub_pattern);

for(int i=0; i<temp.size(); ++i)
result.push_back(leading+temp[i]);
}

return result;
}

这个函数被get_all_results函数调用两次, 一次生成num1, 另一次生成num2, 然后调用check_sum_pattern对结果进行检测, 如果符合sum的模式才是一个合格的解.

vector<vector<string> > get_all_results(const string& num1, const string& num2, const string& sum)
{
vector<vector<string> > output;
Status clean_state;

string s =init(clean_state, num1, num2, sum);
if(s.compare("OK")!=0)
{
cout<<s<<endl;
return output;
}

vector<string> num1_vals = gen_num(clean_state, num1);
if(num1_vals.size()==0)
return output;

for(int i=0; i<num1_vals.size(); ++i)
{
Status curr_state=clean_state;
string v_num1 = num1_vals[i];

set_state(curr_state, v_num1, num1);

vector<string> num2_vals = gen_num(curr_state, num2);
if(num2_vals.size()==0)
continue;

string v_sum;
for(int j=0; j<num2_vals.size(); ++j)
{
string v_num2 = num2_vals[j];
set_state(curr_state, v_num2, num2);

v_sum = str_add(v_num1, v_num2);

if(check_sum_pattern(curr_state, v_sum, sum)==true)
{
vector<string> temp;
temp.push_back(v_num1);
temp.push_back(v_num2);
temp.push_back(v_sum);

output.push_back(temp);
}
else
continue;
}
}
return output;
}

check_sum_pattern函数如下:

bool check_sum_pattern(Status state, const string& v_sum, const string& pattern)
{
if(v_sum.length()!=pattern.length())
return false;
for(int i=0; i<pattern.length(); ++i)
{
if(pattern[i]>='0' && pattern[i]<='9') //pattern is a number, just use it
{
if(v_sum[i]!=pattern[i])
return false;
}
else if(state[pattern[i]] < '0')
{
vector<char> availables;
if(leading_chars.find(pattern[i]) != leading_chars.end())
availables = find_available_choices(state, '1', '9');
else //not a leading char, can be '0'-'9'(if it's unique)
availables = find_available_choices(state, '0', '9');

if(find(availables.begin(), availables.end(), v_sum[i]) != availables.end())
{
state[pattern[i]] = v_sum[i]; // shall I pass this change to the caller???
continue; // NO! it's only used to validate the
} // left digits in this v_sum
else
return false;
}
else if(state[pattern[i]] != v_sum[i])
return false;
}
return true;
}

程序运行还需要一些支持函数, 用于检查输入有效性的check_validity, 用于状态初始化的init, 等. 因为用规则滤掉了那些不符合规则的数, 这个程序在运行性能上还比较令人满意, 但可改进的地方还有很多, 比如根据第一个数的取值来进一步压缩第二个数的取值范围, 等. 另一个可以改进的地方就是减少内存的占用. 因为我的目的是打印出所有解, 因此需要保留中间值, 特别是num1. 可以考虑在求num1时, 当递归结束返回一个有效值时,不保存而直接根据它求num2, 然后在判断结果是否合格, 这样可以大量减少内存使用. 当然, 如果只输出结果的个数, 不需要打印出结果的话, 也就不需要保存结果了. 以后如果有时间在改一下吧, 呵呵.

下面给出完整的代码, 还有一些测试样例:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: