组合数取模小结

本文介绍了如何使用中国剩余定理(CRT)与Lucas定理解决组合数问题,并提供了四个具体的编程实例,涵盖不同的CRT应用及优化技巧。

非常好的学习博客:https://blog.youkuaiyun.com/skywalkert/article/details/52553048

下图转至:https://blog.youkuaiyun.com/Bfk_zr/article/details/78310541

 

例题1.hdu5446:lucas+普通CRT

求c(n,m)%p  1<=n,m.p<=1e18 保证p为最多15个不同pi的乘积  pi<=1e5 

#include<iostream>
#include<stdio.h>
using namespace std;
typedef long long ll;
//求c(n,m)%p  nmp<=1e18 保证p为不同pi的乘积  pi<=1e5 
ll mul(ll a, ll b, ll p) //快速乘,计算a*b%p
{
    ll ret = 0;
    while(b)
    {
        if(b & 1) ret = (ret + a) % p;
        a = (a + a) % p; b >>= 1;
    }
    return ret;
}
void ex_gcd(ll a, ll b, ll &x, ll &y, ll &d)
{
    if (!b){
        d = a, x = 1, y = 0;
    }
    else
    {
        ex_gcd(b, a % b, y, x, d);
        y -= x * (a / b);
    }
}
ll inv(ll t, ll p) //如果不存在,返回-1
{
    ll d, x, y;
    ex_gcd(t, p, x, y, d);
    return d == 1 ? (x % p + p) % p : -1;
}
//n个方程:x=a[i](mod m[i]) (0<=i<n)    m[i]两两互质
ll china(int n, ll *a, ll *m)
{
    ll M = 1, ret = 0;
    for(int i = 0; i < n; i ++) M *= m[i];
    for(int i = 0; i < n; i ++)
    {
        ll w = M / m[i];
        //ret = (ret + w * inv(w, m[i]) * a[i]) % M;
        ret = (ret + mul(w * inv(w, m[i]), a[i], M)) % M;//防爆long long int
    }
    return (ret + M) % M;
}
ll exp_mod(ll a,ll b,ll p)
{
    ll res=1;
    while(b!=0)//利用二进制求乘方
    {
        if(b&1) res=(res*a)%p;
        a=(a*a)%p;
        b>>=1;
    }
    return res;
}
ll comb(ll a,ll b,ll p)
{
    if(a<b) return 0;
    if(a==b) return 1;
    if(b>a-b) b=a-b;//组合的性质,n-m和m是相等的
    ll ans=1,ca=1,cb=1;
    for(ll i=0; i<b; ++i)
    {
        ca=(ca*(a-i))%p;//这里ca表示n!/(n-m)!
        cb=(cb*(b-i))%p;
    }
    ans= (ca*exp_mod(cb,p-2,p))%p;//这里对m!求逆元即可
    return ans;
}
ll lucas(ll n,ll m,ll p)
{
    ll ans=1;
    while(n&&m&&p)
    {
        ans=(ans*comb(n%p,m%p,p))%p;
        n/=p;
        m/=p;
    }
    return ans;
} 
ll mm[100005];//部分解 
ll pp[100005];//质数 
int main()
{
    int T;
    cin>>T;
    ll n,m,k;
    while(T--)
    {
        scanf("%I64d%I64d%I64d",&n,&m,&k);
        ll ans=1;
        for(int i=0; i<k; i++)
        {
        	cin>>pp[i];
            mm[i]=lucas(n,m,pp[i]);
        }
        ans=ans*china(k,mm,pp);
        cout<<ans<<endl; 
    }

}

 

例题2.hdu4773+普通CRT

模数为97*3xxxxx 

#include<iostream>
#include<stdio.h>
using namespace std;
typedef long long ll;

//求C(n+len-1,len,p) n<=1e6  p=97*3xxxxx 
ll mul(ll a, ll b, ll p) //快速乘,计算a*b%p
{
    ll ret = 0;
    while(b)
    {
        if(b & 1) ret = (ret + a) % p;
        a = (a + a) % p;
        b >>= 1;
    }
    return ret;
}

void ex_gcd(ll a, ll b, ll &x, ll &y, ll &d)
{
    if (!b){
        d = a, x = 1, y = 0;
    }
    else
    {
        ex_gcd(b, a % b, y, x, d);
        y -= x * (a / b);
    }
}
ll inv(ll t, ll p) //如果不存在,返回-1
{
    ll d, x, y;
    ex_gcd(t, p, x, y, d);
    return d == 1 ? (x % p + p) % p : -1;
}
//n个方程:x=a[i](mod m[i]) (0<=i<n)    m[i]两两互质
ll china(int n, ll *a, ll *m)
{
    ll M = 1, ret = 0;
    for(int i = 0; i < n; i ++) M *= m[i];
    for(int i = 0; i < n; i ++)
    {
        ll w = M / m[i];
        //ret = (ret + w * inv(w, m[i]) * a[i]) % M;
        ret = (ret + mul(w * inv(w, m[i]), a[i], M)) % M;//防爆long long int
    }
    return (ret + M) % M;
}
ll exp_mod(ll a,ll b,ll p)
{
    ll res=1;
    while(b!=0)//利用二进制求乘方
    {
        if(b&1) res=(res*a)%p;
        a=(a*a)%p;
        b>>=1;
    }
    return res;
}
ll comb(ll a,ll b,ll p)
{
    if(a<b) return 0;
    if(a==b) return 1;
    if(b>a-b) b=a-b;//组合的性质,n-m和m是相等的
    ll ans=1,ca=1,cb=1;
    for(ll i=0; i<b; ++i)
    {
        ca=(ca*(a-i))%p;//这里ca表示n!/(n-m)!
        cb=(cb*(b-i))%p;
    }
    ans= (ca*exp_mod(cb,p-2,p))%p;//这里对m!求逆元即可
    return ans;
}
ll lucas(ll n,ll m,ll p)
{
    ll ans=1;
    while(n&&m&&p)
    {
        ans=(ans*comb(n%p,m%p,p))%p;
        n/=p;
        m/=p;
    }
    return ans;
}
ll a[50];ll mm[3];ll pp[3];
int main()
{
  	ll MOD=364875103;pp[0]=97;pp[1]=3761599;
    int T;cin>>T;
    ll n,m,k;
    int kase=0;
    while(T--)
    {
        scanf("%I64d%I64d%I64d",&n,&m,&k);
        a[k]=m;
        for(int i=0; i<k; i++) {scanf("%I64d",&a[i]);}
        ll ans=1;
        for(int i=0; i<k; i++)
        {
             mm[0]=lucas(a[i+1]-a[i]+n-1,a[i+1]-a[i],pp[0]);
             mm[1]=lucas(a[i+1]-a[i]+n-1,a[i+1]-a[i],pp[1]);
            ans=ans*china(2,mm,pp);
            ans%=MOD;
        }
        printf("Case #%d: ",++kase);  
        cout<<ans<<endl; 
    }
}

 

例题3.洛谷P4720   扩展CRT+lucas

求c(n,m)%p  1<=n,m<=1e18   p<=1e6的任意数 

//C(n,m)%p   n,m<=1e18  p<=1e6的任意数 

#include<cstdio>
using namespace std;
typedef long long ll;
ll n,m,p,x0,y0;
void gcd(ll a,ll b,ll r){
    if(!r){
        x0=0;y0=1;return;
    }
    gcd(b,r,b%r);
    ll tmp=x0;
    x0=y0;
    y0=tmp-a/b*y0;
}
ll inv(ll a,ll mod){
    gcd(a,mod,a%mod);
    x0=(x0%mod+mod)%mod;
    return x0;
}
ll pow(ll a,ll x,ll mod){
    if(x==1) return a;
    if(x==0) return 1;
    ll temp=pow(a,x/2,mod);
    temp=temp*temp%mod;
    if(x%2) temp=temp*a%mod;
    return temp;
}
ll fac(ll a,ll x,ll mod){
    if(!a) return 1;
    ll res=1;
    for(ll i=2;i<=mod;i++)
        if(i%x) res=res*i%mod;
    res=pow(res,a/mod,mod);
    for(ll i=2;i<=a%mod;i++)
        if(i%x) res=res*i%mod;
    return res*fac(a/x,x,mod)%mod;
}
ll C(ll n,ll m,ll x,ll mod){
    ll res=0,t=fac(n,x,mod),temp1=fac(m,x,mod),temp2=fac(n-m,x,mod);
    for(ll i=n;i;i/=x) res+=i/x;
    for(ll i=m;i;i/=x) res-=i/x;
    for(ll i=n-m;i;i/=x) res-=i/x;
    return t*inv(temp1,mod)%mod*inv(temp2,mod)%mod*pow(x,res,mod)%mod;
}
ll crt(ll x,ll mod){
    ll temp=x*inv(p/mod,mod)%p*(p/mod)%p;
    return temp;
}
ll lucas(ll n,ll m){//对每个p^k 进行扩展CRT合并 
    ll res=0,tmp=p,pk;
    for(ll i=2;i*i<=p;i++){
        pk=1;
        if(tmp%i==0){
            while(tmp%i==0){
                tmp/=i;pk*=i;
            }
            res=(res+crt(C(n,m,i,pk),pk))%p;
        }
        if(tmp==1) break;
    }
    if(tmp>1) res=(res+crt(C(n,m,tmp,tmp),tmp))%p;
    return res;
}
int main(){
    scanf("%lld%lld%lld",&n,&m,&p);
    printf("%lld",(lucas(n,m)%p+p)%p);
    return 0;
}

 

例题4.BZOJ1485  线性筛预处理最小质因子求C(2n,n)%p

n<=1e6 ,p<=1e9的任意数

//求C(2n,n)/(n+1)%p  n<=1e6   p<=1e9的任意数  扩展CRT TLE 
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
#include<queue>
#define SF scanf
#define PF printf
using namespace std;
typedef long long LL;
const int MAXN = 2000000;
int Div[MAXN+10], prime[MAXN+10], tot, MOD, n;
int cnt[MAXN+10];
bool vis[MAXN+10];
void init() {
    for(int i = 2; i <= MAXN; i++) {
        if(!vis[i]) prime[++tot] = i;
        for(int j = 1; i * prime[j] <= MAXN; j++) {
            Div[i*prime[j]] = prime[j];
            vis[i*prime[j]] = true;
            if(i % prime[j] == 0) break;
        }
    }
}
// h(n) = C(2n, n) / (n+1)  n<=1e6  P<=1e9 任意数 
int pow_mod(int x, int k) {
    LL ans = 1;
    while(k) {
        if(k & 1) ans = 1LL * ans * x % MOD;
        x = 1LL * x * x % MOD;
        k >>= 1;
    }
    return ans;
}
int main() {
    init();
    SF("%d%d", &n, &MOD);
    for(int i = 2; i <= n; i++) cnt[i] = -1;//分母2*3*4*......n 
    for(int i = n+2; i <= 2*n; i++) cnt[i] = 1;//分子n+2*n+3.....*2n 
    int ans = 1;
    for(int i = n*2; i >= 2; i--)
        if(!vis[i]) ans = 1LL * ans * pow_mod(i, cnt[i]) % MOD;//是质数 
        else cnt[Div[i]] += cnt[i], cnt[i/Div[i]] += cnt[i];//不是质数 
    PF("%d", ans);
}

 

<think>好的,我现在需要解决用户的问题,他想用Python编写一个for循环程序来打印从1到100的数字,每行打印10个,并总结while和for循环的不同。首先,我得先处理打印数字的部分。 用户提到每行打印10个数字,这让我想到可能需要使用循环来控制换行。比如,在每次循环的时候判断当前是否是第10个数字,如果是的话就换行。或者,可以用一个内部的循环来处理每行的10个数字。不过后者可能会更复杂,所以可能还是用单个循环,然后在适当的时候换行更简单。 接下来,我需要考虑如何用for循环遍历1到100。Python中常用的方法是使用range(1, 101),因为range的结束值是不包含的,所以101才能包括100。然后,对于每个数字,我需要打印出来,但不要换行,而是用空格分隔。当打印到第10个、20个等位置时,才换行。 这时候,可以使用enumerate来获索引,判断索引加1是否能被10整除。例如,索引从0开始,当i+1是10的倍数时,换行。不过需要注意,当i是9(第10个元素)时,i+1是10,这时候需要换行。然后,打印当前数字,并在后面加上换行符或者空格。 另外,也可以使用运算来判断是否到了行末。比如,当前数字对10,如果余数是0,则换行。不过这样的话,当数字是10的时候余0,就会在10后面换行,这可能符合要求,因为10是第一个行的最后一个数字。 不过用户要求每行打印10个数字,所以1到10在第一行,11到20在第二行,依此类推。这样的话,当数字是10的倍数时,需要换行。但要注意,例如数字10本身是第一个行的最后一个,所以应该在打印10之后换行。这时候,可以在循环内部检查当前数字是否是10的倍数,如果是的话,换行。但这样的话,第一个换行会在10的时候触发,正确。后面的20、30等也是如此。 不过这样处理的话,最后一个数字100也会触发换行,但可能不会有问题,因为之后没有内容了。 接下来,打印的时候,默认情况下print会自动换行,所以需要修改end参数,让每个数字打印后不换行,而是用空格或其他分隔符。例如,print(num, end=' ')。然后,当需要换行的时候,使用print()来换行。 所以具体的步骤可能是: 1. 使用for循环遍历1到100的数字。 2. 对于每个数字,打印出来,不换行,用空格分隔。 3. 判断当前数字是否是10的倍数,如果是,则换行。 但这样在数字10的时候,会打印10,然后换行。这样第一行就是1到10,每个数字后面有空格,然后换行。不过最后一个数字后面可能也会有一个空格,然后换行,可能会影响格式。例如,在行末会多出一个空格。 或者,可以每行的数字用join方法组合成一个字符串,然后一次性打印。比如,将每10个数字组成一个列表,然后用' '.join(list)来生成字符串,然后打印。这种方法可能更高效,但需要处理分组。 比如,可以将数字分成10个一组,每组用range的步长来处理。例如,外层循环控制行数,内层循环处理每行的10个数字。比如: for i in range(0, 100, 10): line = [str(num) for num in range(i+1, i+11)] print(' '.join(line)) 这样,i从0开始,每次增加10,内层生成i+1到i+10的数字,组成列表,然后转成字符串。不过这种方法需要处理数字的正确范围,比如当i是90时,i+10是100,所以没问题。 这可能更简洁,但需要确保正确生成每个区间的数字。不过这可能更高效,因为减少了循环次数和判断次数。 不过用户的问题可能希望使用一个循环,而不是嵌套循环。所以可能需要用第一种方法,即单循环加判断换行。 接下来,代码实现: 比如: for num in range(1, 101): print(num, end=' ') if num % 10 == 0: print() 这样,每个数字打印后加空格,当遇到10的倍数时换行。但是这样每行的最后一个数字后面会多一个空格,然后换行。比如,第一行的末尾会是“10 ”,然后换行。这可能不太美观,但用户可能可以接受。 或者,可以调整,让每个数字之间用空格分隔,但行末没有空格。这时候需要更复杂的处理。例如,当在第10个位置时,打印数字后直接换行,否则打印空格。但如何判断是否是行末? 可以使用enumerate来获索引,从0开始,每行的第0到第9个元素(共10个)。当索引%10 ==9时换行,否则加空格。 例如: for index, num in enumerate(range(1, 101)): if (index) % 10 == 9: print(num) else: print(num, end=' ') 这样,索引从0到99,当index是9、19、29...99时,也就是每个行的第10个元素,打印数字并换行,其他时候打印数字加空格。这样行末不会有空格。例如,第一个行的数字是1到10,对应的索引是0到9。当index=9时,打印10并换行,其他时候打印num加空格。这样第一行会是:1 2 3 4 5 6 7 8 9 10,然后换行。后面的行类似。 这可能更符合用户的需求,因为行末没有多余的空格。 所以代码可以是这样的: for i in range(1, 101): print(i, end=' ' if i % 10 != 0 else '\n') 或者更简洁地: for num in range(1, 101): print(num, end='\n' if num % 10 == 0 else ' ') 这样,当num是10的倍数时,换行,否则用空格分隔。这样每个数字后面如果是10的倍数就换行,否则空格。例如,10后面换行,20后面换行,等等。但这样的话,最后一个数字100后面也会换行,这可能没问题。 不过测试一下,当num=10时,输出10然后换行,前面的数字1到9用空格分隔,然后10后面换行,这样格式是否正确? 是的,这样每一行都会有10个数字,每个之间用空格分隔,行末换行。 接下来是总结while和for循环的不同之处。用户之前的问题提到了使用while循环计算累加和,现在需要比较两者的区别。 两者的主要区别在于控制循环的方式。for循环通常用于遍历已知的序列(如列表、元组、字符串等)或可迭代对象,循环次数在开始时就已经确定。而while循环则根据条件表达式是否成立来决定是否继续循环,适用于循环次数未知的情况。 例如,while循环需要手动更新控制变量,否则可能导致死循环。而for循环自动迭代,不需要手动控制循环变量。此外,for循环更适用于有明确迭代对象的场景,而while循环适用于需要满足条件才继续执行的场景。 结合引用中的内容,比如引用4提到所有for循环都可以用while改写,但反过来不一定成立。这说明for循环在遍历已知结构时更简洁,而while更灵活,适合不确定次数的情况。 另外,引用3中的猜数字例子使用了while循环,因为循环次数未知,直到用户猜中为止。而引用2中的九九乘法表则使用了嵌套的for循环,因为行和列的数量是已知的。 因此,总结两者的区别: 1. **循环机制**: - `for`循环:遍历可迭代对象中的元素,自动控制迭代次数。 - `while`循环:根据条件判断循环是否继续,需手动更新循环变量。 2. **适用场景**: - `for`循环:处理已知长度的序列(如列表、range生成的数字序列)[^1][^4]。 - `while`循环:适合循环次数不确定的场景(如用户输入验证、游戏循环)[^3]。 3. **控制方式**: - `for`循环不需要手动管理循环变量,避免死循环风险。 - `while`循环必须正确更新条件变量,否则容易导致死循环[^2][^3]。 现在需要将这些总结成用户容易理解的点,并举例说明。 最后,确保代码正确,并且解释清楚。可能还需要测试代码是否正确,例如打印的数字是否正确换行,每行是否是10个数字。 综上,给出Python代码,使用for循环打印1到100,每行10个,然后总结while和for的区别。</think>### Python代码实现 ```python for num in range(1, 101): # 判断是否为每行的第10个数字,用换行符;否则用空格分隔 print(num, end='\n' if num % 10 == 0 else ' ') ``` **代码解释** 1. `range(1, 101)` 生成 $1$ 到 $100$ 的整数序列 2. `end='\n' if num % 10 ==0 else ' '` 控制输出格式:每行第10个数字后换行,其他数字用空格分隔 3. 输出效果示例(前两行): ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 ... 20 ``` --- ### while循环与for循环的核心区别 | **对比维度** | **while循环** | **for循环** | |--------------------|------------------------------------------|------------------------------------------| | **循环控制机制** | 依赖条件表达式,需手动更新控制变量[^3] | 自动遍历可迭代对象(如列表、字符串等)[^1] | | **适用场景** | 循环次数不确定时(如用户输入验证)[^3] | 已知迭代范围时(如遍历序列、固定次数循环)[^4] | | **死循环风险** | 容易因忘记更新变量导致死循环[^2] | 无此风险,自动结束 | | **代码简洁性** | 需要显式初始化、更新变量 | 语法紧凑,无需额外操作 | **典型场景示例** - `while` 循环:猜数字游戏(需持续循环直到猜中)[^3] ```python flag = True while flag: guess = int(input("输入猜测:")) if guess == target: flag = False # 手动终止循环 ``` - `for` 循环:遍历列表或生成固定序列[^2] ```python for i in range(10): # 自动生成0-9的序列 print(i) ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值