您的位置:首页 > 其它

Lucas定理简单运用的五题之hdu3037 hdu 3944 fzu 2020 zoj 3557 hdu4349

2014-07-30 15:33 537 查看
首先Lucas定理的定义及简单证明:选自初等数论第37页



证明的确高大上,蒟蒻想全看懂,那么蒟蒻就不是蒟蒻了,大致应该知道Lucas定理常常用来解决形如C(m,n)%p且m,n数量级大,而p为素数的情况下的组合数学问题,而都知道组合数的形式展开为:(n!/m!*(n-m)!)因为带分母,所以是不能直接取模的,所以得找到m!*(n-m)!的逆元变成乘法形式再来取模才可以保证题目的正确性,所以Lucas定理通过数论中逆元的知识来解决组合数学中的问题,所以还是非常巧妙的,一般求解逆元的方法是用拓展欧几里得来解,因为ax=1(modp)可以写成ax-kp=1的形式,通过解这个不定方程来求解逆元,然而在此一般情况下p为素数,那么由费马小定理知:a^p-1=1(modp),所以a对于p的逆元为a^p-2又可以直接用快速幂求得。通常我们记C(n,m)%p为Lucas(n,m,p)则有:Lucas(n,m,p)=C(n%p,m%p,p)*Lucas(n/p,m/p,p)的形式,递归下去求解可以降低数量级,且有Lucas(x,0,p)=1,下面来简要分析几道题:

HDU4349:求C(n,0)->C(n,n)中,奇数的个数,其中n=10^8,给1S时间,暴力枚举会TLE(我试过,不过根据组合数的对称性枚举一半惊险的过了,但是显然不是正解)

那么继续来分析,对于一个组合数,其是否为奇数,只要%2即可,然后根据Lucas定理的最初含义:C(n,m)%2=C(a
,b
)*C(a[n-1],b[n-1])*......C(a[0],b[0])%2,其中a[i],b[i],代表n,m的2进制形式下的每位的值而且因为是2进制,所以a[i],b[i]是一个0 1 的序列,若要为奇数,则必须有a[i]=1,而此时b[i]无论取0还是1 C(a[i],b[i])都是1否则为0就是偶数了,所以这题赤裸裸的变成了求n的二进制形式下有几个1,累加后求2^cnt即可,复杂度O(logN),1S足矣。。。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;

int main()
{
    int n;
    while(scanf("%d",&n)!=EOF)
    {
        int cnt=0;
        while(n)
        {
            if(n&1)
                cnt++;
            n>>=1;
        }

        printf("%d\n",1<<cnt);
    }
    return 0;
}


HDU 3037

题意:求n个不同元素中取m个进行组合,允许重复下的组合总数,其中m可以取0-m,等价于求x1+x2+x3+...xn=m m属于[0,m]的解的总和,对于单独的一个m,解为

C(n+m-1,m),则sum=C(n-1,0)+C(n,1)+C(n+1,2).....+C(n+m-1,m)

又由:C(n,m)=C(n-1,m)+C(n-1,m-1)可以将sum直接化为:sum=C(n+m,m),但这里的n,m范围是10^9,故要取模p,而取模的过程中就得用Lucas定理边求逆元边求解了。

fzu 2020

题意:裸的求C(n,m)%p的值,方法同上。

zoj 3557

题意:n个元素中取r个构成新集合,不存在任意两个相邻的数在集合中的个数,组合数学书本上的原题了,书上是用映射的思想来将公式推出来的,当时我是用插空法推出来的

公式为C(n-r+1,r),又是直接套基本的Lucas定理可以过得题目。

对于HDU 3037 和ZOJ 3557 这两题的公式推导,卢开澄 卢华明的组合数学第三版的书上的P31-P38有详细解释和组合意义的解释,下面只贴一道题的代码,改一点就分别能过其他两题

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
long long n,m,p;
int t;
long long x,y;
long long ex_gcd(long long a,long long b,long long &x,long long &y)
{
    if(b==0)
    {
        x=1;
        y=0;
        return a;
    }
    long long res,tmp;
    res=ex_gcd(b,a%b,x,y);
    tmp=x;
    x=y;
    y=tmp-(a/b)*y;
    return res;
}//EX_GCD求解
long long inv(long long a,long long p)
{
    long long d=ex_gcd(a,p,x,y);
    return d==1 ? (x%p+p)%p:-1;
}//求逆元
long long C(long long n,long long m,long long p)
{
    long long a=1;
    long long b=1;
    if(m>n)
        return 0;
    while(m)
    {
        a=(a*n)%p;
        b=(b*m)%p;
        m--;
        n--;
    }
    return a*inv(b,p)%p;
}//对组合数进行处理
long long lucas(long long n,long long m,long long p)
{
    if(m==0)
        return 1;
    return C(n%p,m%p,p)*lucas(n/p,m/p,p)%p;
}//递归求解
int main()
{
    cin>>t;
    while(t--)
    {
        cin>>n>>m>>p;
        long long ans=lucas(n,m,p);
        cout<<ans<<endl;
    }
    return 0;
}


但是当你做到hdu3944时,你会发现,输入一个p进来,求一次逆元,当p多次取相等的值,而且TEST的case数很多时,上面的做法将会恶狠狠的TLE,3S都没让过。。。

所以当遇到CASE数多的情况,得乖乖的把阶乘的逆元一个个求出来预处理好,防止重复计算,说白了就是打表。。。

DP?

Time Limit: 10000/3000 MS (Java/Others) Memory Limit: 128000/128000 K (Java/Others)

Total Submission(s): 1865 Accepted Submission(s): 624



Problem Description


Figure 1 shows the Yang Hui Triangle. We number the row from top to bottom 0,1,2,…and the column from left to right 0,1,2,….If using C(n,k) represents the number of row n, column k. The Yang Hui Triangle has a regular pattern as follows.

C(n,0)=C(n,n)=1 (n ≥ 0)

C(n,k)=C(n-1,k-1)+C(n-1,k) (0<k<n)

Write a program that calculates the minimum sum of numbers passed on a route that starts at the top and ends at row n, column k. Each step can go either straight down or diagonally down to the right like figure 2.

As the answer may be very large, you only need to output the answer mod p which is a prime.


Input
Input to the problem will consists of series of up to 100000 data sets. For each data there is a line contains three integers n, k(0<=k<=n<10^9) p(p<10^4 and p is a prime) . Input is terminated by end-of-file.


Output
For every test case, you should output "Case #C: " first, where C indicates the case number and starts at 1.Then output the minimum sum mod p.



Sample Input
1 1 2
4 2 7




Sample Output
Case #1: 0
Case #2: 5


题意从0 0 走到 n ,k处的最小值是多少。

思路:贪心,尽可能的往1走,规律:杨辉三角的对称性和递增性:当k>n-k时,k向上走等价于n-k斜向上走,所以转化为左半部分尽可能的斜向上走的模型,可得:

sum=n-k+C(n,k)+C(n-1,k-1)+...+C(n-k+1,0)。后者等价于C(n+1,k)。

解决办法:预处理阶乘逆元再用Lucas定理求解:不过这个求解逆元预处理的过程是nlogn筛的,还有线性筛法,表示还没怎么懂,以后搞懂了就用线性筛法筛,不过效率只能提高常数级别。。。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
const int maxn=10005;
bool is_prime[maxn];
int prime[maxn];
int cnt;
int pw[maxn];
int n,k,p;
void dabiao()
{
    memset(is_prime,0,sizeof(is_prime));
    is_prime[0]=is_prime[1]=1;
    for(int i=2;i<=maxn;i++)
    {
        if(is_prime[i]==0)
        {
            prime[++cnt]=i;
            pw[i]=cnt;//记录素数位置
            for(int j=2*i;j<=maxn;j+=i)
            {
                is_prime[j]=1;
            }
        }
    }
}
int power(int a,int p,int m)
{
    int res=1;
    while(p)
    {
        if(p&1)
            res=(res*a)%m;
        a=(a*a)%m;
        p>>=1;
    }
    return res;
}//快速幂求逆元
int inv[maxn][maxn];
int fac[maxn][maxn];
void init()
{
    for(int i=1;i<=cnt;i++)
    {
        fac[i][0]=inv[i][0]=1;
        for(int j=1;j<prime[i];j++)
        {
            fac[i][j]=(fac[i][j-1]*j)%prime[i];
            inv[i][j]=power(fac[i][j],prime[i]-2,prime[i]);//费马小定理逆元为a^prime[i]-2
        }
    }
}//预处理的过程
int C(int n,int m,int p)
{
    if(m>n)return 0;
    int t=pw[p];
    return fac[t]
*(inv[t][n-m]*inv[t][m]%p)%p;
}//n!/(n-m)!*m!=n!*inv(n-m,p)*inv(m,p)
int lucas(int n,int m,int p)
{
    if(m==0)
        return 1;
    return C(n%p,m%p,p)*lucas(n/p,m/p,p)%p;
}

int main()
{
    int cas=0;
    dabiao();
    init();
    while(scanf("%d%d%d",&n,&k,&p)!=EOF)
    {
        if(k>n-k)
            k=n-k;
            int ans;
        if(k==0)
        {
            ans=(n+1)%p;
        }

        else
        {

            ans=(lucas(n+1,k,p)+n-k)%p;
        }
        printf("Case #%d: %d\n",++cas,ans);
    }
    return 0;
}
刷了5题,对Lucas定理有了很浅的认识,以后不知道用不用的来,因为人比较笨,所以题目一灵活可能又傻了眼了,而且学长说Lucas根本没啥用,其实都可以暴力求解的,所以蒟蒻学了这个也未必很有用,不过既然在这里集训,不要浪费太多的时间,每天能学一点是一点,智商能提高一丝是一丝,思维能拓宽一些是一些,总比每天过的空虚要好,学弟们都可以秒男人八题了,蒟蒻还是啥都不会,目测离大限之期不远了吧,所以就趁这有限的时间,学点自己能学的东西好了,到大限之时,遗憾也就不会那么多,那么大,可以瞑目了吧。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: