P10380 「ALFR Round 1」D 小山的元力

/*
    历时两天,算是搞出来了
    先贴几个提醒
    首先如果是用lucas定理并用阶乘形式来求组合数的,
    请判断组合数是否成立,即`C(a, b)`,a是否大于等于b
    如果小于你将re几个点
    如果是直接用快速幂求解逆元来做的,恭喜你,你将WA#20和#46,
    因为p可能小于n, m或n + m, 导致你求出的阶乘到了`f[p]`时
    变为0,使得后面的计算错误
    
    进入正题分析题意,首先对于每一堆,当这堆放k个元素的时候
    无论这是哪一堆,它外面的情况总数都是一样的,也就是分配剩
    下n - k个数的情况的数量。
    堆与每一堆都如此,所以我们只需要求出一堆的ans = ai * 总情况数
    在乘上sum = 1! + 2! + ... + m!即可
    那么剩下的就是求总情况数,
    设当前堆放k个数,那么就剩下n - k个数要分配到m - 1个空堆(可以不放)
    这里可以用隔板法,用m - 2个隔板,把剩下n - k个数分成m - 1块
    因为有空堆,而隔板法不能有空的分配,所以可以人为添加m - 1个
    元素,把情况变成必须放,这时候元素有n - k + m - 1个,空隙(两边不算)
    有n - k + m - 1 - 1个,即n - k + m - 2个
    那么剩下的就是在这n - k - 2个空隙里面选m - 2个放上隔板
    就是求C(n - k + m - 2, m - 2),
    
    求组合数有很多方法,当时看数据范围,我直接用的快速幂求通过阶乘求解
    然后因为p的大小寄掉了,也就是上面的提醒
    
    因此这里用lucas定理求解组合数,这样就不会因为q的事情
    寄掉了时间上也够
    因为模数p不变,所以可以先预处理阶乘,但是不要先预处理,逆元
    否则时间复杂度会变成O(plogn)容易TLE
    动态求解逆元,求出组合数。
    最后把组合数乘上ai然后全部相加 = ans
    
    最后输出ans * sum % p即可
    
    注意当m == 1的时候需要特判
    
*/

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long LL;

const int N = 11000100;

int n, m, p;
int f[N];
int sum;

int qmi(int a, int k, int p)
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        k >>= 1;
        a = (LL)a * a % p;
    }
    return res;
}

int in_f(int x) // 逆元
{
    return qmi(x, p - 2, p);
}

int C(int a, int b)
{
    if (a < b) return 0; // 一定要判断,不然会re,可能本地没问题,但那只是越界不够大而已
    return (LL)f[a] * in_f(f[b]) % p * in_f(f[a - b]) % p; 
}

int lucas(int a, int b) // 获取C(a, b)组合数
{
    if (a < p && b < p) return C(a, b);
    return (LL)C(a % p, b % p) * lucas(a / p, b / p) % p;
}


int main()
{
    cin >> n >> m >> p;
    
    if (m == 1) // 特判
    {
        cout << n % p;
        return 0;
    }
    
    f[0] = 1;
    int maxv = max(m, p - 1);
    for (int i = 1; i <= maxv; i ++ )
    {
        f[i] = (LL)f[i - 1] * i % p;
        if (i <= m) sum = (sum + f[i]) % p; 
    }
    
    int ans = 0;
    for (int i = 0; i <= n; i ++ )
    {
        ans = (ans + (LL)i * lucas(n - i + m - 2, m - 2) % p) % p;
    }
    cout << (LL)ans * sum % p << endl;
    return 0;
}

爆了的单用快速幂的算法

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long LL;

const int N = 2100010;

int n, m, p;
int f[N], in_f[N];
int sum;

int qmi(int a, int k, int p)
{
    int res = 1;
    while (k)
    {
        if (k & 1) res = (LL)res * a % p;
        k >>= 1;
        a = (LL)a * a % p;
    }
    return res;
}

int get(int a, int b)
{
    return (LL)f[a] * in_f[b] % p * in_f[a - b] % p; 
}


signed main()
{
    cin >> n >> m >> p;
    
    if (m == 1) // 特判
    {
        cout << n % p;
        return 0;
    }
    
    
    f[0] = in_f[0] = 1;
    for (int i = 1; i <= n + m + 100; i ++ )
    {
        f[i] = (LL)f[i - 1] * i % p;
        in_f[i] = qmi(f[i], p - 2, p)  % p;
        if (i <= m) sum = (sum + f[i]) % p;
    }
    
    if (n == 1)
    {
        cout << sum % p << endl;
        return 0;
    }
    
    int ans = 0;
    for (int i = 0; i <= n; i ++ )
    {
        ans = (LL)(ans + (LL)i * get(n - i + m - 2, m - 2) % p) % p;
    }
    
    cout << (LL)ans * sum % p << endl;
    // cout << get(3, 0);
    return 0;
}
posted @ 2024-04-29 15:42  blind5883  阅读(12)  评论(0编辑  收藏  举报