(lucas) Saving Beans

题目:

Although winter is far away, squirrels have to work day and night to save beans. They need plenty of food to get through those long cold days. After some time the squirrel family thinks that they have to solve a problem. They suppose that they will save beans in n different trees. However, since the food is not sufficient nowadays, they will get no more than m beans. They want to know that how many ways there are to save no more than m beans (they are the same) in n trees.

Now they turn to you for help, you should give them the answer. The result may be extremely huge; you should output the result modulo p, because squirrels can’t recognize large numbers.
Input
The first line contains one integer T, means the number of cases.

Then followed T lines, each line contains three integers n, m, p, means that squirrels will save no more than m same beans in n different trees, 1 <= n, m <= 1000000000, 1 < p < 100000 and p is guaranteed to be a prime.
Output
You should output the answer modulo p.
Sample Input
2
1 2 5
2 1 5
Sample Output
3
3

Hint
For sample 1, squirrels will put no more than 2 beans in one tree. Since trees are different, we can label them as 1, 2 … and so on.
The 3 ways are: put no beans, put 1 bean in tree 1 and put 2 beans in tree 1. For sample 2, the 3 ways are:
put no beans, put 1 bean in tree 1 and put 1 bean in tree 2.

分析与解答

1.先说一下lucas
参考:https://blog.csdn.net/wyg1997/article/details/52152282
https://blog.csdn.net/wyg1997/article/details/52152282
lucas定理:

(图片来自https://blog.csdn.net/arrowlll/article/details/53064748
其实我们想一下二进制,111:就是1* 2^0+1*2^1+1*2^2
这个定理也是一样的akak-1…..a1a0就是p进制的每一位
所以有如下解释:

A、B是非负整数,p是质数。AB写成p进制:A=a[n]a[n-1]…a[0],B=b[n]b[n-1]…b[0] 这里的每一个数组元素表示其p进制的每一位。
则组合数C(A,B)=C(a[n],b[n])C(a[n-1],b[n-1])…*C(a[0],b[0])。也就是说,把大组合数问题变成了一个个的小组合数。(A,B小于mod)

其实可以想一下快速幂,里面%2就是取二进制的最后一位数,/2就是二进制向前移动一位,所以怎么实现代码也和快速幂类似,如下:

表达式:C(n,m)%p=C(n/p,m/p)*C(n%p,m%p)%p。(可以递归)
这里如果n/p和m/p很大,我们仍然可去调用lucas
递归方程:(C(n%p, m%p)*Lucas(n/p, m/p))%p。(递归出口为m==0,return 1)

LL Lucas(LL n,LL k)     //Lucas定理递归 
{
    if (k == 0)     //递归终止条件 
        return 1;
    else
        return C(n % mod , k % mod) * Lucas(n / mod , k / mod) % mod;
}

对于每一个c(n,k)我们要写一个函数也就是求(n! / ( k! * ( n - k)! ) )%mod,由于有除法取模,所以我们要求k!*(n - k)!的逆元。
这里用小费马求逆元,我们还需写一个快速幂。由于组合数里面有阶乘,所以我们还需打一个阶乘表

整体下来就是这个样子
这里对mod取模

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define CLR(a,b) memset(a,b,sizeof(a))
#define INF 0x3f3f3f3f
#define LL long long
LL mod = 1000003;
LL fac[1000000+11] = {1,1};
void getfac()       //打一个阶乘表 
{
    for (int i = 2 ; i <= 1000000 ; i++)
        fac[i] = fac[i-1] * i % mod;
}
LL quick_mod(LL n , LL m)       //求快速幂 
{
    LL ans = 1;
    n %= mod;
    while (m)
    {
        if (m & 1)
            ans = ans * n % mod;
        n = n * n % mod;
        m >>= 1;
    }
    return ans;
}
LL C(LL n , LL k)       //费马小定理求逆元 
{
    if (k > n)
        return 0;
    else
        return fac[n] * (quick_mod(fac[k] * fac[n-k] % mod , mod - 2)) % mod;
}
LL Lucas(LL n,LL k)     //Lucas定理递归 
{
    if (k == 0)     //递归终止条件 
        return 1;
    else
        return C(n % mod , k % mod) * Lucas(n / mod , k / mod) % mod;
}
int main()
{
    getfac();
    LL n,k;
    int Case = 1;
    int u;
    scanf ("%d",&u);
    while (u--)
    {
        scanf ("%lld %lld",&n,&k);
        printf ("Case %d: %lld\n",Case++,Lucas(n,k));
    }
    return 0;
}

2.再说一下这个题
m个小球放到n个盒子,盒子可以为空, 方案数就是:C(n+m-1,n-1)
这个题是m个果子放到n个树上,类比一下,数就是盒子,小球就是果子,那么方案数就是C(n+m-1,n-1)
现在这个果子可以是0到m个,
所以总方案数
=C(n+m-1,n-1)+C(n+m-2,n-1)+…+C(n+1,n-1)+C(n,n-1)+C(n-1,n-1)
有一个组合数公式c(m,n)=c(m-1,n-1)+c(m-1,n)
这个组合数公式也可以看作:C(m,n-1)+C(m,n)=C(m+1,n)
还有个组合数公式:C(n,n)=1
所以我们从总方案数的后面变形
C(n-1,n-1)=C(n,n)
C(n-1,n-1)+C(n-1,n-1)=C(n,n)+C(n,n-1)=C(n+1,n)
现在后三个
C(n+1,n-1)+C(n,n-1)+C(n-1,n-1)=C(n+1,n-1)+C(n+1,n)=C(n+2,n)
我们发现,C(n+1,n-1)+C(n,n-1)+C(n-1,n-1)
这里m从2到0,上式等于C(n+2,n)
那么我们推广到整个式子
C(n+m-1,n-1)+C(n+m-2,n-1)+…+C(n+1,n-1)+C(n,n-1)+C(n-1,n-1)
这里m从m到0,式子等于C(n+m,n)
推到这我们现在就只需求
C(n+m,m) % p

(我觉得我应该去学数学。。我爱数学啊)
现在这个题p不是固定的每次都会变,所以我们不能用打表了
C(n,m)=n!/(m!(n-m)!)
=(n*(n-1) (n-m+1))/m!
分子从n-0一直到n-(m-1)也就是说循环m-1-0+1次也就是m次,而分母m!从1乘到m也是m次,那么我们就可以每次通过一个循环计算出C(n,m)了

ll C(ll n,ll m,ll p){
    if(m==0) return 1;
    if(m>n-m) m=n-m;
    ll up=1,down=1;
    for(int i=1;i<=m;i++){//这里不能打表了 
        up = (up*(n-i+1))%p;
        down=(down*i)%p; 
    }
    return up*Pow(down,p-2,p)%p;
}

参考:
https://blog.csdn.net/qq_27717967/article/details/51493877
这个是wrong answer 我也不知道为什么

//当取模的p小于1e9时,不用预处理
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
typedef long long ll;
//计算n的k次方对M取模,二分法
ll Pow(ll n, ll k, ll p){
    ll ans=1;
    while(k){
        if(k&1)
            ans=(ans*n)%p;
        n = (n*n)%p;
        k>>=1; //k=k>>1  k=k/2;
    }
    return ans;
}
ll C(ll n,ll m,ll p){
    if(m==0) return 1;
    if(m>n-m) m=n-m;
    ll up=1,down=1;
    for(int i=1;i<=m;i++){//这里不能打表了 
        up = (up*(n-i+1))%p;
        down=(down*i)%p; 
    }
    return up*Pow(down,p-2,p)%p;
}
ll lucas(ll n,ll m,ll p){
    if(m==0) return 1;
    return C(n%p,m%p,p)*lucas(n/p,m/p,p);
}
int main(){
    int t=1;
    ll m,n,p;
    scanf("%d",&t);
    while(t--){
        scanf("%I64d%I64d%I64d",&n,&m,&p);
        printf("%I64d\n",lucas(n+m,m,p));
    }
    return 0;
}

后来改成预处理,lucas也改变,才对。
但是这个代码改到上一个代码的那个题上,就runtime error
我也不知道为什么,玄学就玄学到这了

#include <iostream>
#include <string.h>
#include <cmath>
#define ll long long
using namespace std;
const int maxn=100002;
ll n,m,p;
ll fac[maxn];

void getfac(ll p)//预处理阶层
{
    fac[0]=1;
    for(int i=1;i<=p;i++)
        fac[i]=fac[i-1]*i%p;
}

ll power(ll a,ll n,ll p)//快速幂运算
{
    ll ans=1;
    while(n)
    {
        if(n&1)
            ans=ans*a%p;
        a=a*a%p;
        n/=2;
    }
    return ans;
}

ll lucas(ll n,ll m,ll p)
{
    ll ans=1;
    while(n&&m)
    {
        ll a=n%p;
        ll b=m%p;
        if(a<b) return 0;
        ans=(ans*fac[a]*power(fac[b]*fac[a-b]%p,p-2,p))%p;//  fac[b]*fac[a-b]后面别忘了%p,否则WA
        n/=p;
        m/=p;
    }
    return ans;
}


int main()
{
    int t;cin>>t;
    while(t--)
    {
        cin>>n>>m>>p;
        getfac(p);
        cout<<lucas(n+m,m,p)<<endl;
    }
    return 0;
}

输入数据第一行是一个正整数T,表示数据组数 (T <= 100) 接下来是T组数据,每组数据有3个正整数 n, m, p (1 <= m <= n <= 10^9, m <= 10^4, m < p < 10^9, p是素数)第一个代码对

1 <= n, m <= 1000000000, 1 < p < 100000 and p is guaranteed to be a prime.第二个代码对

猜你喜欢

转载自blog.csdn.net/qq_40828914/article/details/81807778