洛谷P5684 非回文串 题解 组合数学的另一种解法

题目链接:https://www.luogu.com.cn/problem/P5684

原先的解法见:https://www.cnblogs.com/quanjun/p/12396279.html

其实只是换了另外一种思考方式,不过我个人感觉这个比较好理解囧。

题目描述

Alice 有 (n) 个字符,它们都是英文小写字母,从 (1 sim n) 编号,分别为 (c_1,c_2, cdots , c_n)
Bob 准备将这些字符重新排列,组成一个字符串 (S)。Bob 知道 Alice 有强迫症,所以他打算将 (S) 组成一个非回文串来折磨 Alice。

现在 Bob 想知道他共有多少种不同的排列字符的方案,能使得 (S) 是个非回文串。一种排列字符的方案指的是一个 (1 sim n) 的排列 (p_i) ,它所组成的 (S = c_{p_1}c_{p_2} cdots c_{p_n})

一个字符串是非回文串,当且仅当它的逆序串与原串不同。例如 (abcda) 的逆序串为 (adcba),与原串不同,故 (abcda) 是非回文串。而 (abcba) 的逆序串与原串相同,是回文串。

由于最后的结果可能很大,你只需要告诉 Bob 总方案数对 (10^9+7) 取模后的值。

解题思路

首先判断字符串 (S) 能否组成回文串。

判断方法是开一个 (cnt[]) 数组,(cnt[0]) 用于记录字符 '(a)' 出现的次数,(cnt[1]) 用于记录字符 '(b)' 出现的次数,……,(cnt[25]) 用于记录字符 '(z)' 出现的次数。

如果 (cnt[0])(cnt[25]) 中奇数的个数大于 (1) ,那么 (S) 没有办法构成回文串。

此时,字符串 (S) 的任意排列都是非回文串,答案为 (A_{n}^{n} = n!)

(S) 可以组成回文串的前提下,我们可以计算有多少种方案可以构成回文串。然后拿总的方案数 (n!) 减去回文串的方案数就是我们的答案了。

那么如何计算回文串的排列方案数呢?

首先我们只需要确定前 (lfloor frac{n}{2} floor) 个数的排列,那么因为回文串是对称的,后 (lceil frac{n}{2} ceil) 个数在哪些位置应该放 '(a)',哪些位置应该放 '(b)',……,哪些位置应该放 '(z)'也就确定了。

其次,为了凑这 (lfloor frac{n}{2} floor) 个数,我们需要:

  • 选出 (lfloor frac{cnt[0]}{2} floor) 个 '(a)' (Rightarrow) 对应的方案数为 (C_{cnt[0]}^{lfloor frac{cnt[0]}{2} floor})
  • 选出 (lfloor frac{cnt[1]}{2} floor) 个 '(b)' (Rightarrow) 对应的方案数为 (C_{cnt[1]}^{lfloor frac{cnt[1]}{2} floor})
  • 选出 (lfloor frac{cnt[2]}{2} floor) 个 '(c)' (Rightarrow) 对应的方案数为 (C_{cnt[2]}^{lfloor frac{cnt[2]}{2} floor})
  • ……
  • 选出 (lfloor frac{cnt[25]}{2} floor) 个 '(z)' (Rightarrow) 对应的方案数为 (C_{cnt[25]}^{lfloor frac{cnt[25]}{2} floor})

(lfloor frac{n}{2} floor) 个数的排列数为 (lfloor frac{n}{2} floor !)

对于第 (i) 个字符,虽然它在后半部分放的位置确定了,但是这 (lceil frac{cnt[i]}{2} ceil) 个元素彼此之间的先后顺序是没有确定的,所以还需要考虑第 (i) 个字符在后半部分回文串中的排列方案数 (lceil frac{cnt[i]}{2} ceil !)

综上所述,总的方案数为

[n! - lfloor frac{n}{2} ceil ! imes prod_{i=0}^{25} C_{cnt[i]}^{lfloor frac{cnt[i]}{2} floor} cdot lceil frac{cnt[i]}{2} ceil ! ]

一些说明

因为代码中涉及到除法取模,所以需要用到扩展欧几里得算法来实现求逆, 代码实现中的 gcd 函数是扩展欧几里得,inv 函数用于求解 a 在模 n 条件下的逆。

实现代码如下:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2020;
const long long MOD = 1000000007LL;
typedef long long ll;
void gcd(ll a , ll b , ll &d , ll &x , ll &y) {
    if(!b) {d = a; x = 1; y = 0;}
    else { gcd(b , a%b,d,y , x); y -= x * (a/b); }
}
ll inv(ll a , ll n) {
    ll d , x , y;
    gcd(a , n , d,  x , y);
    return d == 1 ? (x+n)%n : -1;
}
ll f[maxn];
int n, cnt[26];
string s;
void init() {
    f[0] = 1;
    for (int i = 1; i < maxn; i ++) f[i] = f[i-1] * i % MOD;
}
ll C(int n, int m) {
    ll res = f[n];
    res = res * inv(f[n-m], MOD) % MOD;
    res = res * inv(f[m], MOD) % MOD;
    return res;
}
int main() {
    init();
    cin >> n >> s;
    for (int i = 0; i < n; i ++) cnt[s[i] - 'a'] ++;
    int cc = 0;
    for (int i = 0; i < 26; i ++) if (cnt[i]%2) cc ++;
    if (cc > 1) {
        cout << f[n] << endl;
        return 0;
    }
    ll tmp = f[n/2];
    for (int i = 0; i < 26; i ++) if (cnt[i]) {
        tmp = tmp * C(cnt[i], cnt[i]/2) % MOD;
        tmp = tmp * f[cnt[i] - cnt[i]/2] % MOD;
    }
    cout << (f[n] - tmp + MOD) % MOD << endl;
    return 0;
}
原文地址:https://www.cnblogs.com/quanjun/p/12402525.html