码迷,mamicode.com
首页 > 其他好文 > 详细

@bzoj - 2142@ 礼物

时间:2019-07-07 21:34:15      阅读:172      评论:0      收藏:0      [点我收藏+]

标签:iostream   ssi   表达   for   scanf   can   air   script   possible   


@description@

一年一度的圣诞节快要来到了。每年的圣诞节小E都会收到许多礼物,当然他也会送出许多礼物。不同的人物在小E心目中的重要性不同,在小E心中分量越重的人,收到的礼物会越多。
小E从商店中购买了n件礼物,打算送给m个人,其中送给第i个人礼物数量为wi。
请你帮忙计算出送礼物的方案数(两个方案被认为是不同的,当且仅当存在某个人在这两种方案中收到的礼物不同)。
由于方案数可能会很大,你只需要输出模P后的结果。

Input
输入的第一行包含一个正整数P,表示模;
第二行包含两个整整数n和m,分别表示小E从商店购买的礼物数和接受礼物的人数;
以下m行每行仅包含一个正整数wi,表示小E要送给第i个人的礼物数量。

Output
若不存在可行方案,则输出“Impossible”,否则输出一个整数,表示模P后的方案数。

Sample Input
100
4 2
1
2

Sample Output
12

【样例说明】
下面是对样例1的说明。
以“/”分割,“/”前后分别表示送给第一个人和第二个人的礼物编号。12种方案详情如下:
1/23 1/24 1/34
2/13 2/14 2/34
3/12 3/14 3/24
4/12 4/13 4/23

【数据规模和约定】
设P=p1^c1 * p2^c2 * p3^c3 * … *pt ^ ct,pi为质数。
对于100%的数据,1≤n≤10^9,1≤m≤5,1≤pi^ci≤10^5。

@solution@

令 sum = 所有的 wi 之和,则答案 ans 的表达式如下:
\[\frac{n!}{w_1!*w_2!*...*w_m!*(n-sum)!}\]

当 sum > n 时无解。问题转换为求上式对 P 取模的值。
此时我们需要扩展 lucas 定理(因为 P 不一定是质数所以要扩展的)。
(mark 一下yhn学长的博客)。

首先可以将 P 唯一分解一下,得到 P = p1^c1 * p2^c2 * p3^c3 * … * pt^ct。然后分开求再中国剩余定理合并(中国剩余定理自行百度可以学)。
那么实际上我们只需要考虑模数为质数的幂的情况,不妨设模数为 p^c。
我们通过扩展 lucas 将阶乘 n! 拆写成 A * p^B 的形式,其中 A 与 p 互质。这样的形式下做阶乘之间的除法,只需要 A 部分直接逆元,B 部分直接减即可。

怎么才能将阶乘 n! 拆写成 A * p^B 的形式呢?
先将 \(n! = 1*2*...*n\) 写下,然后将其中 p 的倍数共同提出一个 p 因子来,得到 \(n! = (n/p)!*p^{n/p}*1*2*...*(p-1)*(p+1)*...\)
注意到此时整个式子可以分成三部分:\((n/p)!\) 这一部分递归求解;\(p^{n/p}\) 这一部分融入上面的 B;\(1*2*...*(p-1)*(p+1)*...\) 这一部分融入上面的 A。
但是第三部分还是不好求。继续观察发现第三部分是有循环节的(因为是在模 p^c 的意义下的),求出循环节的个数、循环节内的乘积、剩余部分的乘积即可。

或许有人认为 lucas 定理只适用于组合数,但通过上文我们可以发现,扩展意义下的 lucas 定理可以用于模意义下阶乘之间的乘除。
也许改天可以用这个性质出个什么题。

@accepted code@

#include<cstdio>
#include<vector>
#include<iostream>
using namespace std;
const int MAXN = 100000;
typedef pair<int, int> pii;
int pow_mod(int b, int p, int m) {
    int ret = 1;
    while( p ) {
        if( p & 1 ) ret = 1LL*ret*b%m;
        b = 1LL*b*b%m;
        p >>= 1;
    }
    return ret;
}
vector<int>vec;
int fct[MAXN + 5];
pii lucas(int x, int b, int m) {
    if( x == 0 ) return make_pair(1, 0);
    pii tmp = lucas(x/b, b, m);
    return make_pair(1LL*tmp.first*pow_mod(fct[m-1], x/m, m)%m*fct[x%m]%m, tmp.second+x/b);
}
int P, n, m;
int main() {
    scanf("%d%d%d", &P, &n, &m);
    int tmp = P, sum = 0, ans = 0;
    for(int i=1;i<=m;i++) {
        int x; scanf("%d", &x);
        vec.push_back(x), sum += x;
    }
    if( sum > n ) {
        puts("Impossible");
        return 0;
    }
    vec.push_back(n-sum);
    for(int i=2;i<=MAXN&&i<=tmp;i++)
        if( tmp % i == 0 ) {
            int m = 1;
            while( tmp % i == 0 )
                tmp /= i, m *= i;
            fct[0] = 1;
            for(int j=1;j<m;j++) {
                fct[j] = fct[j-1];
                if( j % i ) fct[j] = 1LL*fct[j]*j%m;
            }
            pii res = lucas(n, i, m);
            for(int j=0;j<vec.size();j++) {
                pii tmp2 = lucas(vec[j], i, m);
                res.first = 1LL*res.first*pow_mod(tmp2.first, m - m/i - 1, m)%m;
                res.second -= tmp2.second;
            }
            int del = 1LL*res.first*pow_mod(i, res.second, m)%m;
            ans = (ans + 1LL*(P/m)*pow_mod(P/m, m - m/i - 1, m)%P*del%P)%P;
        }
    printf("%d\n", ans);
}

@details@

题目中真的没有 P 的范围。。。找了很久都没找到。。。

看到网上的题解大多 P 用的是 long long,还以为我开 int 过不了。
结果其实过得了的。。。

@bzoj - 2142@ 礼物

标签:iostream   ssi   表达   for   scanf   can   air   script   possible   

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11147882.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!