3

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

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

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

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

输入格式:
包含输入字符串的单行

输出格式:
单行包含.palindrome % (109 + 7)

约束:

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

样本输入 01:

aaabbbb

样本输出 01:

3 

解释:
给定字符串的三个排列是回文,可以给出为 abbabba 、 bbaaabb 和 bababab。

样本输入 02:

cdcdcdcdeeeef

样本输出 02:

90

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

我的算法如下:

  1. 存储每个字符的频率(只需要查看字符串的一半,因为后半部分应该与回文的 def 的第一个相同)
  2. 如果出现多个奇数次的字符,则不可能出现回文
  3. 否则对于每个字符的频率增量计算回文数(动态编程)

对于 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;
}

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

4

2 回答 2

9

请注意,字谜是自反的(它们从后面看和从前面看是一样的),所以每个字符的一半出现在一侧,我们只需要计算这些排列的数量。另一面将是这一面的完全相反,因此它不会增加可能性的数量。一个字符的奇数出现(如果存在的话)必须总是在中间,所以在计算排列数时可以忽略它。

所以:

  • 计算字符的频率

  • 检查是否只有 1 个奇数频率

  • 划分每个字符的频率(向下舍入 - 删除任何奇怪的出现)。

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

    公式

由于上述项可能会变得很大,我们可能希望将公式拆分为主要因素,以便我们可以取消项,因此我们只剩下乘法,然后我相信我们可以在每次乘法之后,这应该适合(因为)。% 109 + 7long long(109+7)*105 < 9223372036854775807

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

请注意,这是一个素数,因此我们可以使用费马小定理来计算 的乘法逆元,然后乘以这些并在每一步取模而不是除法。. 使用平方取幂,这将非常快。p = 109 + 7mi mod pmi-1 = mi(10^9 + 5) (mod p)

我还发现了这个问题(其中也有一些有用的重复项)。

例子:

Input: aaabbbb

Frequency:
a  b
3  4

Div 2:
a  b
1  2

Solution:
3!/2!1! = 3

Input: cdcdcdcdeeeef

Frequency:
c  d  e  f
4  4  4  1

Div 2:
c  d  e  f
2  2  2  0

Solution:
6!/2!2!2!0! = 90
于 2013-07-08T10:51:23.480 回答
5

基本公式是:

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

其中plength/2单词的下限,a,b,c..,z 表示单词中出现频率 a,b,c..,z 的一半的下限。

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

于 2013-07-08T15:29:44.773 回答