在C ++中查找作为字谜的回文数量

时间:2013-07-08 10:27:25

标签: c++ string algorithm dynamic-programming palindrome

我正在http://www.hackerrank.com参加在线编程竞赛,以获得乐趣。我正在研究的问题之一是找到一些字符串字符串的字谜。我已经解决了这个问题但是当我尝试上传它时,它在许多测试用例中都失败了。由于实际的测试用例对我来说是隐藏的,我不知道为什么它失败但我认为这可能是一个可扩展性问题。现在我不希望有人给我一个现成的解决方案,因为这不公平,但我只是对人们对我的方法的看法感到好奇。

以下是网站上的问题描述:

  

既然国王知道如何找出某个词是否有一个回文或不回文的字谜,他就面临着另一个挑战。他意识到,对于给定的单词,可能存在多个字谜。你能帮他找出一个特定单词可能有多少字谜吗?

     国王说了很多话。对于每个给定的单词,国王需要找出作为回文的字符串的字符数。由于字谜的数量可能很大,国王需要字谜数%(10 9 + 7)。

     

输入格式:
  一行包含输入字符串

     

输出格式:
  一行包含palindrome % (109 + 7)的anagram字符串数。

     

约束:

     
      
  • 1<=length of string <= 105
  •   
  • 字符串的每个字符都是小写字母。
  •   
  • 每个测试用例至少有1个anagram,这是一个回文。
  •   
     

示例输入01:

aaabbbb
     

样品输出01:

3 
     

说明:
  作为回文的给定字符串的三个排列可以作为abbabba,bbaaabb和bababab给出。

     

示例输入02:

cdcdcdcdeeeef
     

示例输出02:

90

如问题描述中所指定的,输入字符串可以大到10 ^ 5,因此大字符串的所有回文都不可能,因为我将遇到数字饱和问题。此外,因为我只需要给出一个基于模(10 ^ 9 + 7)的模数答案,我想用基数(10 ^ 9 + 7)记录数字,并且在所有计算之后采用基数的分数部分(10 ^ 9) + 7)答案,因为那将是模数。

我的算法如下:

  1. 存储每个字符的频率(仅需要查看字符串的一半) 后半部分应与第一部分相同(对照回文)
  2. 如果有多个字符出现且奇数个时间没有回文可能
  3. 对于每个char的频率,还要逐步计算回文数(动态编程)
  4. 对于DP以下是子问题: previous_count = 1

      

    每增加一个字符added number of palindromes = previous_count * (number_of_char_already_seen + 1)/(number of char same as current char)

    这是我的代码:

    #include <iostream>
    #include <string>
    #include <vector>
    #include <map>
    #include <cmath>
    #include <cstdio>
    #include <algorithm>
    #include <fstream>
    using namespace std;
    #define MAX_SIZE 100001
    
    void factorial2 (unsigned int num, unsigned int *fact) {
        fact[num]++;
        return;
    }
    
    double mylog(double x) {
        double normalizer = 1000000007.0; 
        return log10(x)/log10(normalizer);
    }
    
    int main() {
        string in;
        cin >> in;
        if (in.size() == 1) { 
            cout << 1 << endl;
            return 0;
        }
        map<char, int> freq;
        for(int i=0; i<in.size(); ++i) {
            if (freq.find(in.at(i)) == freq.end()) {
                freq[in.at(i)] = 1;
            } else {
                freq[in.at(i)]++;
            }
        }
        map<char, int> ::iterator itr = freq.begin();
        unsigned long long int count = 1;
        bool first = true;
        unsigned long long int normalizer = 1000000007;
        unsigned long long int size = 0;
        unsigned int fact[MAX_SIZE] = {0};
        vector<unsigned int> numerator;
        while (itr != freq.end()) {
            if (first == true) {
                first = false;
            } else {
                for (size_t i=1; i<=itr->second/2; ++i) {
                    factorial2(i, fact);
                    numerator.push_back(size+i);
                }
            }
            size += itr->second/2;
            ++itr;
        }
        //This loop is to cancel out common factors in numerator and denominator
        for (int i=MAX_SIZE-1; i>1; --i) {
            while (fact[i] != 0) {
                bool not_div = true;
                vector<unsigned int> newNumerator;
                for (size_t j=0; j<numerator.size(); ++j) {
                    if (fact[i] && numerator[j]%i == 0) {
                        if (numerator[j]/i > 1)
                            newNumerator.push_back(numerator[j]/i);
                        fact[i]--;  //Do as many cancellations as possible
                        not_div = false;
                    } else {
                        newNumerator.push_back(numerator[j]);
                    }
                }
                numerator = newNumerator;
                if (not_div) {
                    break;
                }
            }
        }
        double countD = 0.0;
        for (size_t i=0; i<numerator.size(); ++i) {
            countD += mylog(double(numerator[i]));
        }
        for (size_t i=2; i <MAX_SIZE; ++i) {
            if (fact[i]) {
                countD -= mylog((pow(double(i), double(fact[i]))));
                fact[i] = 0;
            }
        }
        //Get the fraction part of countD
        countD = countD - floor(countD);
        countD = pow(double(normalizer), countD);
        if (floor(countD + 0.5) > floor(countD)) {
            countD = ceil(countD);
        } else {
            countD = floor(countD);
        }
        count = countD;
        cout << count;
        return 0;
    }
    

    现在我花了很多时间在这个问题上,我只是想知道我的方法是否有问题,或者我在这里遗漏了什么。有什么想法吗?

2 个答案:

答案 0 :(得分:9)

请注意,字谜是自反的(它们从背面看起来与从正面看起来相同),因此每个字符的一半出现在一侧,我们只需要计算这些字符的排列数。另一方将是这方面的精确反转,因此它不会增加可能性的数量。奇数出现的字符(如果存在)必须始终位于中间,因此在计算排列数时可以忽略它。

所以:

  • 计算字符的频率

  • 检查频率只有1个奇数

  • 除以每个字符的频率(向下舍入 - 删除任何奇数事件)。

  • 使用以下公式计算这些字符的排列:
    (根据Wikipedia - multiset permutation

    Formula

由于上面的术语可能会变得很大,我们可能希望将公式分解为素数因子,这样我们就可以取消术语,这样我们只剩下乘法,那么我相信我们可以{{1}每次乘法后,它应该适合% 109 + 7(从long long开始)。

感谢IVlad指出了一种比上述方法更有效的避免溢出的方法:

  

请注意,(109+7)*105 < 9223372036854775807是素数,因此我们可以使用Fermat's little theorem来计算p = 109 + 7的乘法逆,并乘以这些并在每一步取代mod而不是分。 mi mod p。通过平方使用取幂,这将非常快。

我还发现this question(也有一些有用的副本)。

示例:

mi-1 = mi(10^9 + 5) (mod p)

答案 1 :(得分:5)

基本公式是:

p!/(a!*b!...*z!)

其中p是单词的length/2的下限和a,b,c ..,z表示出现a,b,c ..,z的频率的一半的楼层接受的话。

现在剩下的唯一问题是如何计算。为此,请检查this