acm-复杂度

来源:互联网 发布:java设置字符集编码 编辑:程序博客网 时间:2024/05/22 12:39

求逆元

   在计算(a/b)%Mod时,往往需要先计算b%Mod的逆元p(b有逆元的条件是gcd(b,Mod)==1,显然素数肯定有逆元),然后由(a*p)%Mod得结果c。这里b的逆元p满足(b*p)%Mod=1。先来简单证明一下:

(a/b)%Mod=c;   (b*p)%Mod=1;   ==》   (a/b)*(b*p)%Mod=c;   ==》   (a*p)%Mod=c;

从上面可以看出结论的正确性,当然这里b需要是a的因子。接下来就需要知道根据b和Mod,我们怎么计算逆元p了。扩展欧几里德算法,大家应该都知道,就是已知a、b,求一组解(x,y)使得a*x+b*y=1。这里求得的x即为a%b的逆元,y为b%a的逆元(想想为什么?把方程两边都模上b或a看看)。调用ExtGcd(b,Mod,x,y),x即为b%Mod的逆元p。

   求b%Mod的逆元p还有另外一种方法,即p=b^(Mod-2)%Mod,因为b^(Mod-1)%Mod=1(这里需要Mod为素数)。

首先给出这个Lucas定理:

 

A、B是非负整数,p是质数。AB写成p进制:A=a[n]a[n-1]...a[0],B=b[n]b[n-1]...b[0]。
则组合数C(A,B)与C(a[n],b[n])*C(a[n-1],b[n-1])*...*C(a[0],b[0])  modp同余

即:Lucas(n,m,p)=c(n%p,m%p)*Lucas(n/p,m/p,p) 

这个定理的证明不是很简单,我一直想找个很好的张明,但是,没找到,昨天看到了一个解题报告,基本上可以说明白这个Lucas定理是怎么回事了,具体的是说:

以求解n! %p为例,把n分段,每p个一段,每一段求的结果是一样的。但是需要单独处理每一段的末尾p, 2p,...,把p提取出来,会发现剩下的数正好又是(n /p)!,相当于划归成了一个子问题,这样递归求解即可。

这个是单独处理n!的情况,当然C(n,m)就是n!/(m!*(n-m)!),每一个阶乘都用上面的方法处理的话,就是Lucas定理了,注意这儿的p是素数是有必要的。

Lucas最大的数据处理能力是p在10^5左右,不能再大了,hdu3037就是10^5级别的!


对于大组合数取模,n,m不大于10^5的话,用逆元的方法,可以解决。对于n,m大于10^5的话,那么要求p<10^5,这样就是Lucas定理了,将n,m转化到10^5以内解。

然后再大的数据,我就不会了!

复杂度

时间限制:1000 ms  内存限制:65535 KB
难度:3
描述

for(i=1;i<=n;i++)

  for(j=i+1;j<=n;j++)

    for(k=j+1;k<=n;k++)

       operation;

你知道 operation 共执行了多少次吗;

输入
输入 m 和n 表示m为for循环的层数,n为for中的n。
(n,m<=2000),输入以n==0和m==0结束
输出
输出operation执行的次数(输入结果mod 1009)
样例输入
2 31 32 40 0
样例输出
336
上传者
changhongli

我的代码:(没有ac,想复杂了,坑啊)

#include
using namespace std;
#define mod 1009
int Multi[1010];
void init() 

    Multi[0] =1; 
    for(int i =1;i <= mod;i++) 
       Multi[i] = Multi[i-1]*i%mod; 
}
int mod_inverse(int s,int t,int n)
{
 int res=1;
 int re=s;
 while(t)
 {
  if(t&1)
   res=(res*re)%n;
  re=(re*re)%n;
  t>>=1; 
 }
 return res;
}
int Lucas(int m,int n)
{
 int a=m%mod;
 int b=n%mod;
 returnMulti[b]*mod_inverse(Multi[a]*Multi[b-a]%mod,mod-2,mod)%mod;  
}
int main()
{
 int m,n;
 init();
 while(cin>>m>>n)
 {
  if(m==0&&n==0) 
   return0;
  else
  {
   intflag=1;
   while(m&&n)
   {
    flag*=Lucas(m,n); 
    m/=mod;
    n/=mod;
   }
   cout<<flag<<endl; 
  
 }
 return 0;
}

ac的代码:(网上的,记录一下)

解题思路:

这是我队友chl出的一道题目,刚开始认为是找规律问题,然后就开始考虑找规律,昨天晚上找到一个自认为非常完美的规律,结果今天早上来发现是错的,后来经他提醒终于知道了,这一题需要用到组合数学的知识。。

首先m层,每层都有一个值i、j、k….我们发现这m个值是不重复的,而且是递增序列,于是我们可以想到只需要计算总共有多少中m个数的组合的情况即可,即C(m,n)  —– 从n中任选m个数的种类数

但是问题又来了,n和m的值最大为2000,因此求C(m,n) 的时候long long 也存不下,而且(n%mod)/(m%mod)!= (n/m)%mod;

这就又需要用到另外的一个公式

C(m,n)= C (m,n-1) + C (m-1,n-1);

根据递推公式打表完美解决

#include
#include
using namespace std;
#define N 1009
int res[2001][2001]={0};
void f()
{
 for(int i=1;i<=2000;i++)
  for(intj=1;j<=2000;j++)
  {
   if(i==j) 
   {
    res[i][j]=1;
    continue; 
   }
   if(i==1)
   {
    res[i][j]=j%N;
    continue; 
   }
   res[i][j]=(res[i][j-1]+res[i-1][j-1])%N;
  }
}
int main()
{
 f();
 int m,n;
 while(scanf("%d%d",&m,&n),m+n)
 {
  printf("%d\n",res[m][n]); 
 }
 return 0;
}

原创粉丝点击