• 51nod 1362 搬箱子——[ 推式子+组合数计算方法 ] [ 拉格朗日插值 ]


    题目:http://www.51nod.com/Challenge/Problem.html#!#problemId=1362

    方法一:

      设 a 是向下走的步数、 b 是向右下走的步数、 c 是向下走的步数。如果是走到第 j 列的方案数的话,有:

      ( a+b = n )    ( b+c = j )

      所以枚举 a 和 j , b 和 c 的值就是确定的,可以用组合数算:

      ( sumlimits_{i=0}^{n}sumlimits_{j=0}^{m}C_{i+j}^{i}*C_{j}^{n-i} )

      ( = sumlimits_{i=0}^{n}sumlimits_{j=0}^{m} frac{(i+j)!}{i!j!} * frac{j!}{(n-i)!(i+j-n)!} )

      ( = sumlimits_{i=0}^{n}sumlimits_{j=0}^{m} frac{(i+j)!}{i!(n-i)!(i+j-n)!} )

      一看就是要把只和 i 有关的提到前面。而且分子和分母好像能凑成新的组合数,所以弄一个 ( n! )

      ( = sumlimits_{i=0}^{n} frac{n!}{i!(n-i)!} sumlimits_{j=0}^{m} frac{(i+j)!}{n!(i+j-n)!} )

      ( = sumlimits_{i=0}^{n} C_{n}^{i} sumlimits_{j=0}^{m} C_{i+j}^{n} )

      ( = sumlimits_{i=0}^{n} C_{n}^{i} * C_{i+m+1}^{n+1} )

      但 i+m+1 很大,而且模数也不是质数。所以用扩展Lucas。

      但它的 pk 可以很大,会TLE。总之弄了半天还是会T一个点。反正本来复杂度也不对……

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    using namespace std;
    const int N=805,M=1e5+5;
    int n,m,mod,tot,p[M],pk[M],ans;
    void init()
    {
      int d=mod; tot=0;
      for(int i=2;i*i<=d;i++)
        if(d%i==0)
          {
        p[++tot]=i;pk[tot]=1;
        while(d%i==0)d/=i,pk[tot]*=i;
          }
      if(d>1)p[++tot]=pk[tot]=d;
    }
    int pw(int x,int k,int md)
    {int ret=1;while(k){if(k&1)ret=(ll)ret*x%mod;x=(ll)x*x%mod;k>>=1;}return ret;}
    void exgcd(int a,int b,int &x,int &y)
    {if(!b){x=1;y=0;return;}  exgcd(b,a%b,y,x); y-=a/b*x;}
    int inv(int a,int md){int x,y;exgcd(a,md,x,y);return (x+md)%md;}
    int multi(int n,int p,int pk)
    {
      if(!n)return 1;
      int sm=1;
      for(int i=2;i<pk;i++)if(i%p)sm=(ll)sm*i%pk;//if
      sm=pw(sm,n/pk,pk);
      for(int i=2,j=n%pk;i<=j;i++)if(i%p)sm=(ll)sm*i%pk;//
      return (ll)sm*multi(n/p,p,pk)%pk;
    }
    int exlcs(int n,int m,int p,int pk)
    {
      int sm=0;
      for(int i=n;i;i/=p)sm+=i/p;
      for(int i=m;i;i/=p)sm-=i/p;
      for(int i=n-m;i;i/=p)sm-=i/p;
      return (ll)pw(p,sm,pk)*multi(n,p,pk)%pk*inv(multi(m,p,pk),pk)%pk*inv(multi(n-m,p,pk),pk)%pk;
    }
    int C(int n,int m,int p)
    {
      if(n<m)return 0;
      int ret,sm=1;
      for(int i=2;i<=n;i++)sm=(ll)sm*i%p; ret=sm;
      sm=1; for(int i=2;i<=m;i++)sm=(ll)sm*i%p;
      ret=(ll)ret*pw(sm,p-2,p)%p;
      sm=1; for(int i=2;i<=n-m;i++)sm=(ll)sm*i%p;
      ret=(ll)ret*pw(sm,p-2,p)%p;
      return ret;
    }
    int lcs(int n,int m,int p)
    {
      if(n<m)return 0; if(!m||n==m)return 1;
      return (ll)C(n%p,m%p,p)*lcs(n/p,m/p,p)%p;
    }
    int calc(int n,int m)
    {
      if(n<m)return 0;/////
      int ret=0;
      for(int i=1;i<=tot;i++)
        {
          if(p[i]==pk[i])
        {
          ret=(ret+(ll)lcs(n,m,p[i])*(mod/pk[i])%mod*inv(mod/pk[i],pk[i]))%mod;
        }
          else
        ret=(ret+(ll)exlcs(n,m,p[i],pk[i])*(mod/pk[i])%mod*inv(mod/pk[i],pk[i]))%mod;
        }
      return ret;
    }
    int main()
    {
      while(scanf("%d%d%d",&n,&m,&mod)==3)
        {
          init(); ans=0;
          for(int i=0;i<=n;i++)
        {
          ans=(ans+(ll)calc(n,i)*calc(i+m+1,n+1))%mod;
        }
          printf("%d
    ",ans);
        }
      return 0;
    }
    View Code

      发现别人都不是这样写的。

      因为 ( C_{i+m+1}^{n+1} ) 虽然 i+m+1 很大,但n+1 很小,所以可以枚举分子上的数(约掉分母里很大的那一项之后只剩下 n 项了!),一边把含的 p 拿出来之类的。

      注意不要用求阶乘里 p 的个数的方法求好总的 p 的个数之后在枚举的时候跳过 %p==0 的数。因为可能那个数除掉一些 p 之后有剩下的。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    using namespace std;
    const int N=805,M=1e5+5;
    int n,m,mod,tot,p[M],pk[M],ans;
    void init()
    {
      int d=mod; tot=0;
      for(int i=2;i*i<=d;i++)
        if(d%i==0)
          {
        p[++tot]=i;pk[tot]=1;
        while(d%i==0)d/=i,pk[tot]*=i;
          }
      if(d>1)p[++tot]=pk[tot]=d;
    }
    int pw(int x,int k,int md)
    {int ret=1;while(k){if(k&1)ret=(ll)ret*x%mod;x=(ll)x*x%mod;k>>=1;}return ret;}
    void exgcd(int a,int b,int &x,int &y)
    {if(!b){x=1;y=0;return;}  exgcd(b,a%b,y,x); y-=a/b*x;}
    int inv(int a,int md){int x,y;exgcd(a,md,x,y);return (x%md+md)%md;}
    int C(int n,int m,int p,int pk)
    {
      int nm=0,ret=1;
      for(int i=n,j=1;j<=m;i--,j++)
        {
          int a=i,b=j;
          while(a%p==0)nm++,a/=p;
          while(b%p==0)nm--,b/=p;
          ret=(ll)ret*a%pk*inv(b,pk)%pk;
        }
      ret=(ll)ret*pw(p,nm,pk)%pk;
      return ret;
    }
    int calc(int n,int m)
    {
      if(n<m)return 0;/////
      int ret=0;
      for(int i=1;i<=tot;i++)
        {
          ret=(ret+(ll)C(n,m,p[i],pk[i])*(mod/pk[i])%mod*inv(mod/pk[i],pk[i]))%mod;
        }
      return ret;
    }
    int main()
    {
      while(scanf("%d%d%d",&n,&m,&mod)==3)
        {
          init(); ans=0;
          for(int i=0;i<=n;i++)
        {
          ans=(ans+(ll)calc(n,i)*calc(i+m+1,n+1))%mod;
        }
          printf("%d
    ",ans);
        }
      return 0;
    }
    View Code

     方法二:

      设 dp[ i ][ j ] 表示走到第 i 行第 j 列的方案数,则有 dp[ i ][ j ] = dp[ i-1 ][ j ] + dp[ i ][ j-1 ] + dp[ i-1 ][ j-1 ] ;

      用差分的方法判断一下,发现 dp[ i ] 是一个 i 次的多项式。(设原数列为第0次差分后数列,若差分 k 次后数列变成常数列(即每一项值都一样),则为 k 次多项式)

      设 ( s[ i ][ j ] = sumlimits_{k=0}^{j} dp[ i ][ k ] ) ,则 s[ i ] 是一个 i+1 次多项式。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    using namespace std;
    const int N=805,M=35;
    int n,m,mod,dp[N][N];
    void upd(int &x){x>=mod?x-=mod:0;}
    int main()
    {
      while(scanf("%d%d%d",&n,&m,&mod)==3)
        {
          memset(dp,0,sizeof dp);
          int d=min(n+1,m); dp[0][0]=1;
          for(int i=0;i<=n;i++)
        for(int j=0;j<=d;j++)
          {
            if(i)dp[i][j]+=dp[i-1][j],upd(dp[i][j]);
            if(j)dp[i][j]+=dp[i][j-1],upd(dp[i][j]);
            if(i&&j)dp[i][j]+=dp[i-1][j-1],upd(dp[i][j]);
          }
    
          for(int j=0;j<=d;j++)dp[n][j]+=dp[n][j-1],upd(dp[n][j]);
          int lm=1000;
          for(int i=1,R=d-1;i<=lm;i++,R--)
        {
          int flag=1;
          for(int j=0;j<=R;j++)
            {
              dp[n][j]=dp[n][j+1]-dp[n][j];
              if(j&&dp[n][j]!=dp[n][j-1])flag=0;
            }
          if(flag){printf("%d
    ",i);break;}
        }
        }
      return 0;
    }
    判断

      所以可以用拉格朗日插值。

      注意 ( i - j ) 可能没有逆元,同样采用把 mod 质因数(只有log(mod)个质因数!)分解、最后乘上 pk 的方法。

      注意要除的东西不能先累乘起来最后再除掉。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    using namespace std;
    const int N=805,M=35;
    int n,m,mod,tot,dp[N][N],p[M],nm[M],phi;
    void upd(int &x){x>=mod?x-=mod:0;}
    int pw(int x,int k)
    {int ret=1;while(k){if(k&1)ret=(ll)ret*x%mod;x=(ll)x*x%mod;k>>=1;}return ret;}
    void init()
    {
      tot=0; phi=mod; int k=mod;
      for(int i=2;i*i<=k;i++)
        if(k%i==0)
          {
        p[++tot]=i;
        phi/=i; phi*=(i-1);
        while(k%i==0)k/=i;
          }
      if(k>1)p[++tot]=k,phi/=k,phi*=(k-1);
    }
    void add(int a,int fx,int &ret)
    {
      for(int i=1;i<=tot;i++)
        {
          while(a%p[i]==0)nm[i]+=fx,a/=p[i];
        }
      fx==1? ret=(ll)ret*a%mod : ret=(ll)ret*pw(a,phi-1)%mod ;
    }
    int cz(int ret)
    {
      for(int i=1;i<=tot;i++)
        {
          ret=(ll)ret*pw(p[i],nm[i])%mod;
          nm[i]=0;
        }
      return ret;
    }
    int main()
    {
      while(scanf("%d%d%d",&n,&m,&mod)==3)
        {
          memset(dp,0,sizeof dp);
          int d=min(n+1,m); dp[0][0]=1;
          for(int i=0;i<=n;i++)
        for(int j=0;j<=d;j++)
          {
            if(i)dp[i][j]+=dp[i-1][j],upd(dp[i][j]);
            if(j)dp[i][j]+=dp[i][j-1],upd(dp[i][j]);
            if(i&&j)dp[i][j]+=dp[i-1][j-1],upd(dp[i][j]);
          }
          for(int i=1;i<=d;i++)
        dp[n][i]+=dp[n][i-1],upd(dp[n][i]);
          if(m==d){printf("%d
    ",dp[n][m]);continue;}
    
          init();  int ans=0;
          for(int i=0;i<=d;i++)
        {
          int ret=1;
          for(int j=0;j<=d;j++)
            {
              if(i==j)continue;
              add(m-j,1,ret);  add(i-j,-1,ret);
            }
          ans=(ans+(ll)dp[n][i]*cz(ret))%mod;
        }
          if(ans<0)ans+=mod;
          printf("%d
    ",ans);
        }
      return 0;
    }
    View Code
  • 相关阅读:
    20145312 实验四《Andoid开发基础》
    20145312 《Java程序设计》第九周学习总结
    20145312 《Java程序设计》第八周学习总结
    20145312 实验三《敏捷开发与XP实践》
    20145312 《Java程序设计》第七周学习总结
    20145312 实验二《 Java面向对象程序设计》
    20145309李昊 信息安全系统设计基础第1周学习总结
    20145309《信息安全系统设计基础》第0周学习总结
    20145303课程总结
    20145215刘俊谦实验五 Java网络编程及安全
  • 原文地址:https://www.cnblogs.com/Narh/p/10063072.html
Copyright © 2020-2023  润新知