Codeforces554C:Kyoya and Colored Balls(组合数学+费马小定理)

本文探讨了在特定条件下的组合数学问题,通过实例展示了如何解决由不同颜色球组成的排列问题,采用组合数学原理和费马小定理优化计算过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Kyoya Ootori has a bag with n colored balls that are colored with k different colors. The colors are labeled from 1 to k. Balls of the same color are indistinguishable. He draws balls from the bag one by one until the bag is empty. He noticed that he drew the last ball of color ibefore drawing the last ball of color i + 1 for all i from 1 to k - 1. Now he wonders how many different ways this can happen.

Input

The first line of input will have one integer k (1 ≤ k ≤ 1000) the number of colors.

Then, k lines will follow. The i-th line will contain ci, the number of balls of the i-th color (1 ≤ ci ≤ 1000).

The total number of balls doesn't exceed 1000.

Output

A single integer, the number of ways that Kyoya can draw the balls from the bag as described in the statement, modulo 1 000 000 007.

Sample test(s)
input
3
2
2
1
output
3
input
4
1
2
3
4
output
1680
Note

In the first sample, we have 2 balls of color 1, 2 balls of color 2, and 1 ball of color 3. The three ways for Kyoya are:

1 2 1 2 3
1 1 2 2 3
2 1 1 2 3

题意:
有k种颜色,每种颜色对应a[i]个球,球的总数不超过1000
要求第i种颜色的最后一个球,其后面接着的必须是第i+1种颜色的球
问一共有多少种排法

思路:
首先我们容易想到我们必须要确定每种颜色最后一个球的放法
所有对于最后一种颜色,假设这种颜色有b个球,而总球数为a
那么必然有一个球是放在最后一个位置的,那么剩下的球就是z=C(b-1,a-1)种方法
那么对于倒数第二种球,假设有x个,此时总球数位y=a-b
那么之前已经有z种方法了,而对于每一种放法,此时倒数第二种颜色拿出一个作为最后一个球的话,它对于每种放法必然只有一个固定方法,位置是最后一个没有放球的位置,这样既保证放法符合要求,而剩下的球就有C(x-1,y-1)种放法
然后相乘得到最后一种颜色与最后第二种颜色的方法,以此类推。。
可以使用费马小定理来优化组合数计算

#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define LL long long
const LL mod =  1000000007;
LL n;
LL a[1005];
LL fac[1000005];


LL ppow(LL a,LL b)
{
    LL c=1;
    while(b)
    {
        if(b&1) c=c*a%mod;
        b>>=1;
        a=a*a%mod;
    }
    return c;
}


LL work(LL m,LL i)
{
    return ((fac[m]%mod)*(ppow((fac[i]*fac[m-i])%mod,mod-2)%mod))%mod;
}

int main()
{
    LL i,j,k;
    fac[0] = 1;
    for(i = 1; i<1000005; i++)
        fac[i]=(fac[i-1]*i)%mod;
    LL ans = 1,sum = 0;
    scanf("%I64d",&n);
    for(i = 1; i<=n; i++)
    {
        scanf("%I64d",&a[i]);
        sum+=a[i];
    }
    for(i = n; i>=1; i--)
    {
        ans*=work(sum-1,a[i]-1);
        ans%=mod;
        sum-=a[i];
    }
    printf("%I64d\n",ans);

    return 0;
}


### Codeforces 上与费马小定理相关的题目及其实现 #### 费马小定理简介 费马小定理指出,如果 $p$ 是质数且 $a$ 不是 $p$ 的倍数,则有: $$ a^{p-1} \equiv 1 \ (\text{mod}\ p) $$ 由此可以推导出,在模 $p$ 下求逆元的方法:对于任意整数 $a$ (满足 $\gcd(a, p) = 1$),其在模 $p$ 意义下的逆元为 $a^{p-2}$。 --- #### 经典题目解析与实现 以下是几道经典的 Codeforces 题目以及它们基于费马小定理的解决方法: --- ##### **题目一:Codeforces 1380G** 该题要求计算一系列操作后的结果,并涉及大量取模运算。其中的关键在于利用费马小定理快速求解逆元[^3]。 ###### 解决方案 通过预处理阶乘及其逆元数组,可以在常数时间内完成组合数的查询。下面是完整的代码实现: ```cpp #include <bits/stdc++.h> using namespace std; typedef long long LL; const int MAXN = 3e5 + 5; const LL MOD = 998244353; // 快速幂模板 LL qpow(LL a, LL b){ LL res = 1; while(b){ if(b & 1) res = res * a % MOD; a = a * a % MOD; b >>= 1; } return res; } LL fact[MAXN], inv_fact[MAXN]; void preprocess(){ fact[0] = 1; for(int i=1;i<MAXN;i++) fact[i] = fact[i-1]*i%MOD; inv_fact[MAXN-1] = qpow(fact[MAXN-1], MOD-2); for(int i=MAXN-2;i>=0;i--){ inv_fact[i] = inv_fact[i+1]*(i+1)%MOD; } } // 计算组合数 C(n,k) mod MOD LL comb(LL n, LL k){ if(k > n || k < 0) return 0; return fact[n] * inv_fact[k] % MOD * inv_fact[n-k] % MOD; } int main(){ ios::sync_with_stdio(false); cin.tie(0); preprocess(); // 输入处理... } ``` 此代码片段实现了对阶乘和逆元的预处理功能,从而能够高效地支持后续的大规模组合数计算需求[^3]。 --- ##### **题目二:Advertising Agency (Codeforces Round #697 Div. 3)** 这是一道关于广告公司分配预算的问题,核心思路也是围绕着组合数展开讨论[^4]。 ###### 解决方案 由于直接计算可能会超出计算机所能表达的最大数值范围,因此采用动态规划配合费马小定理间接获取答案成为必然选择之一。下面给出了一种可行的做法: ```cpp #include<bits/stdc++.h> using namespace std; const int N=2e5+5,MOD=1e9+7; long long fac[N],inv[N]; vector<int>v; long long power(long long x,long long y){ long long ret=1; while(y){ if(y&1)ret=(ret*x)%MOD; x=x*x%MOD;y>>=1; } return ret; } void precompute(){ fac[0]=fac[1]=1; for(int i=2;i<N;i++)fac[i]=(fac[i-1]*i)%MOD; inv[N-1]=power(fac[N-1],MOD-2); for(int i=N-2;i>=0;i--)inv[i]=(inv[i+1]*(i+1))%MOD; } long long C(int n,int r){ if(r>n||r<0)return 0; return ((fac[n]*inv[r]%MOD)*inv[n-r])%MOD; } int main(){ precompute(); int T,n,m,x,y; scanf("%d",&T); while(T--){ v.clear(); scanf("%d%d%d%d",&n,&m,&x,&y); for(int i=1;i<=n;i++){ int tmp;scanf("%d",&tmp); if(tmp<=y)v.push_back(tmp); } sort(v.begin(),v.end()); long long ans=0; for(int i=v.size();i>0;i--){ if((long long)i*m>x)break; ans+=C(v.size()-i+i-1,i-1); ans%=MOD; } printf("%lld\n",ans); } } ``` 这里不仅包含了基本的幂次方函数定义,还额外加入了针对特定条件筛选有效元素列表的操作步骤。 --- #### 总结 上述两例充分体现了费马小定理在实际竞赛编程中的广泛应用价值——无论是用来加速单步除法操作还是辅助构建更大规模的数据结构模型都极为重要! ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值