• @bzoj



    @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≤109,1≤m≤5,1≤pici≤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
    ", ans);
    }
    

    @details@

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

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

  • 相关阅读:
    jsp第六周作业
    jsp第四周作业
    jsp第一周周作业
    第一次软件测试课堂练习
    4.11jsp
    第六周作业
    第三周jsp作业
    3.10 jsp作业
    3.4软件测试
    JSP第六周作业
  • 原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/11147882.html
Copyright © 2020-2023  润新知