zoukankan      html  css  js  c++  java
  • 洛谷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;
    }
    
  • 相关阅读:
    导出预设体的缩略图工具
    FairyGui自动生成Wnd工具(Unity)
    Unity骨骼优化(转)
    顶点处理机制
    面向对象
    文件读取
    协同程序
    元表
    模块与包
    Lua表
  • 原文地址:https://www.cnblogs.com/quanjun/p/12402525.html
Copyright © 2011-2022 走看看