【组合数学】 ACM组合数学题单

容斥原理:12

Lucas定理:3

1.How many integers can you find HDU - 1796

题意: 有一个包含m(m<=10)个数的集合,问小于N的数中,有多少数能被集合中至少一个整数整除。

分析:

N N N以内可以整除 x x x的数有 ( N − 1 ) / x (N-1)/x (N1)/x个;
N N N以内可以同时整除 x x x y y y的数有 ( N − 1 ) / l c m ( x , y ) (N-1)/lcm(x,y) (N1)/lcm(x,y)个;
N N N以内可以整除 x x x y y y的数有 ( N − 1 ) / x − ( N − 1 ) / l c m ( x , y ) (N-1)/x-(N-1)/lcm(x,y) (N1)/x(N1)/lcm(x,y)

注: / / /表示整除

利用容斥原理与二进制枚举即解
坑点:集合中的数可能含0,不特殊判断会RE

代码:

int a[25];
int gcd(int a,int b){return a%b==0?b:gcd(b,a%b);}
int lcm(int a,int b){return a/gcd(a,b)*b;}
int main(){
    int N,M;
    while(~scanf("%d%d",&N,&M)){
        N--;
        int ans=0,num=0;
        for(int i=0;i<M;i++) {
            int temp;
            scanf("%d",&temp);
            if(temp) a[num++]=temp;
        }
        for(int i=1;i<(1<<num);i++){
           int cnt=0;
           int temp=1;
           for(int j=0;j<num;j++)
                if(i&(1<<j))
                   cnt++,temp=lcm(temp,a[j]);
           if(!temp) continue;
           if(cnt%2) ans+=N/temp;
           else ans-=N/temp;
        }
        printf("%d\n",ans);
    }
    return 0;
}

2.Co-prime HDU - 4135

题意: 求整数 A A A B B B中,与 N N N互质的个数

分析:

c a l ( X ) cal(X) cal(X) 1 1 1 X X X中与 N N N互质的个数,则题目转化为求 c a l ( B ) − c a l ( A − 1 ) cal(B)-cal(A-1) cal(B)cal(A1)

N N N互质等价于不和N含有相同的质因子

N N N不互质的个数更好求:

N N N质因子分解,得到 N = p 1 p 2 p 3 ⋅ ⋅ ⋅ p x N=p_1p_2p_3\cdot\cdot\cdot p_x N=p1p2p3px,则 1 1 1 X X X中有 X / p 1 X/p_1 X/p1个数含因子 p 1 p_1 p1,即不与 N N N互质,然后容斥原理即可

代码:

int Prime[100005];
bool Isprime[100005];
int cnt=0,num=0;
long long a,b,n;
int  fact[105];

void get_prime(){
  for(int i=2;i<=100000;i++) Isprime[i]=1;
  for(int i=2;i<=100000;i++){
    if(Isprime[i]) Prime[cnt++]=i;
    for(int j=0;j<cnt&&Prime[j]*i<=100000;j++){
        Isprime[Prime[j]*i]=0;
        if(i%Prime[j]==0) break;
    }
  }
}

long long cal(long long x){ //1到x中有多少数和n互质
    long long ret=x;
    for(int i=1;i<(1<<num);i++){
        int _cnt=0;
        int mul=1;
        for(int j=0;j<num;j++)
            if(i&(1<<j))
                _cnt++,mul*=fact[j];
        if(_cnt%2) ret-=x/mul;
        else ret+=x/mul;
    }
    return ret;
}

int main(){
    get_prime();
    int T;
    scanf("%d",&T);
    for(int Case=1;Case<=T;Case++){
        scanf("%lld%lld%lld",&a,&b,&n);
        num=0;
        long long temp=n;
        for(int i=0;i<cnt&&Prime[i]*Prime[i]<=temp;i++)
            if(temp%Prime[i]==0){
                fact[num++]=Prime[i];
                while(temp%Prime[i]==0)
                    temp/=Prime[i];
            }
        if(temp>1) fact[num++]=temp;
        printf("Case #%d: %lld\n",Case,cal(b)-cal(a-1));
    }
    return 0;
}

3.DP? HDU - 3944

题意: 杨辉三角形中,在每个点可以选择向下或向右下走,求从 ( 0 , 0 ) (0,0) (0,0)走到点 ( n , k ) (n,k) (n,k)经过的数字和的最小值。

在这里插入图片描述
分析:

从顶点到点 ( n , k ) (n,k) (n,k)需要向下移动 n n n格,向右移动 k k k格,并且只有向右下移动才能实现右移。所以一定会进行 k k k次向右下移动, n − k n-k nk次向下移动。

由于杨辉三角形的对称性,因此可以只考虑 2 ∗ k < = n 2*k<=n 2k<=n的情况下,若 2 ∗ k > n 2*k>n 2k>n,则令 k = n − k k=n-k k=nk

易得,向右下移动这一操作越晚进行越好,所以答案为 n − k + C ( n , k ) + C ( n − 1 , k − 1 ) + … … + C ( n − k + 1 , 1 ) + C ( n − k , 0 ) n-k+C(n,k)+C(n-1,k-1)+……+C(n-k+1,1)+C(n-k,0) nk+C(n,k)+C(n1,k1)++C(nk+1,1)+C(nk,0)

由于 C ( n − k , 0 ) = C ( n − k + 1 , 0 ) C(n-k,0)=C(n-k+1,0) C(nk,0)=C(nk+1,0)

所以原式可以化简为 n − k + C ( n + 1 , k ) n-k+C(n+1,k) nk+C(n+1,k)

由于输入的组数较多,所以这道题需要预处理。直接预处理阶乘是不现实的(因为 n n n m m m 1 e 9 1e9 1e9,而且模数是会变化的)。因为模数 p p p( p < 10000 p<10000 p<10000)比较小,所以使用卢卡斯定理,就只需要处理10000以内的组合数

代码:

#include <cstdio>
#include <cmath>
using namespace std;

int fact[10005][1505];
int inv[10005][1505];
int mod;
int Prime[10005];
bool Isprime[10005];
int id[10005];
int pid;
int cnt=0;

void get_prime(){
  for(int i=2;i<=10000;i++) Isprime[i]=1;
  for(int i=2;i<=10000;i++){
    if(Isprime[i]) id[i]=cnt,Prime[cnt++]=i;
    for(int j=0;j<cnt&&Prime[j]*i<=10000;j++){
        Isprime[Prime[j]*i]=0;
        if(i%Prime[j]==0) break;
    }
  }
}
int quickpow(int base,int power,int mod){
   int ret=1;
   while(power){
    if(power&1) ret=ret*base%mod;
    base=base*base%mod;
    power>>=1;
   }
   return ret;
}
int _inv(int x,int p){return quickpow(x,p-2,p)%p;}

void _pre(){
  get_prime();
  for(int i=0;i<cnt;i++) fact[0][i]=1,inv[0][i]=1;
  for(int i=0;i<cnt;i++)
    for(int j=1;j<Prime[i];j++){
        fact[j][i]=fact[j-1][i]*j%Prime[i];
        inv[j][i]=_inv(fact[j][i],Prime[i]);
    }
}

int C(int n,int m){
  if(m>n) return 0;
  return fact[n][pid]*inv[m][pid]%mod*inv[n-m][pid]%mod;
}

int Lucas(int n,int m){
    if(m==0)  return 1;
    return Lucas(n/mod,m/mod)*C(n%mod,m%mod)%mod;
}


int main(){
    _pre();
    int n,k;
    int Case=1;
    while(~scanf("%d%d%d",&n,&k,&mod)){
        pid=id[mod];
        if(2*k>n) k=n-k;
        printf("Case #%d: %d\n",Case++,(n-k+Lucas(n+1,k))%mod);
    }
    return 0;
}

4.Mysterious For HDU - 4373

题意:

有两种循环:

for(int i=0;i<n;i++) //第一种
for(int i=0;i<n;i++) //第一种
   for(int j=i;j<n;j++) //第二种

现已知共有 m m m个循环进行嵌套,其中 k k k个循环是第一种,每个循环的上限为 n n n,最内层循环中有一个函数 f ( x ) f(x) f(x),求该函数被调用了几次

分析:

一个第一种循环后跟了 t t t个第二种循环时,很容易通过找规律发现循环次数为 C ( n + t − 1 , t ) C(n+t-1,t) C(n+t1,t)

由于模数为 364875103 = 97 ∗ 3761599 364875103=97*3761599 364875103=973761599,并非质数

代码:

#include <cstdio>
#include <cmath>
using namespace std;

int pos[105];
const long long  mod1=97;
const long long  mod2=3761599;
const long long  Mod=mod1*mod2;
long long fact1[105];
long long fact2[3000005];
long long e1,e2;

long long quickpow(long long base,long long power,long long mod){
   long long ret=1;
   while(power){
    if(power&1) ret=ret*base%mod;
    base=base*base%mod;
    power>>=1;
   }
   return ret;
}

long long exgcd(long long a,long long b,long long &x,long long &y){
   if(a%b==0){x=0;y=1;return b;}
   long long d=exgcd(b,a%b,x,y);
   long long temp=x;
   x=y;y=temp-a/b*y;
   return d;
}

long long inv(long long m,long long mod){ //求m在模mod下的逆元
   long long x,y;
   long long d=exgcd(m,mod,x,y);
   if(d!=1) return -1; //若不互质则无逆元
   long long temp=mod/d;
   return (x%temp+temp)%temp; //返回最小正整数解
}


long long C(long long n,long long m,long long mod){
  if(m>n) return 0;
  if(mod==97) return fact1[n]*inv(fact1[m]*fact1[n-m]%mod,mod)%mod;
  return fact2[n]*inv(fact2[m]*fact2[n-m],mod)%mod;
}

long long Lucas(long long n,long long m,long long mod){
  if(!m) return 1;
  return Lucas(n/mod,m/mod,mod)*C(n%mod,m%mod,mod)%mod;
}

void _pre(){
  fact1[0]=1,fact2[0]=1;
  for(int i=1;i<=96;i++) fact1[i]=fact1[i-1]*i%mod1;
  for(int i=1;i<=3000000;i++) fact2[i]=fact2[i-1]*i%mod2;
  e1=quickpow(mod2,mod1-2,mod1)*mod2;
  e2=quickpow(mod1,mod2-2,mod2)*mod1;
}


int main(){
   _pre();
    int T;
    scanf("%d",&T);
    for(int Case=1;Case<=T;Case++){
        int n,m;
        scanf("%d%d",&n,&m);
        int k;
        scanf("%d",&k);
        for(int i=0;i<k;i++) scanf("%d",&pos[i]);
        pos[k++]=m;
        long long ans=1;
        for(int i=1;i<k;i++){
            long long temp=pos[i]-pos[i-1];
            long long a=Lucas(n+temp-1,temp,mod1)*e1%Mod;
            long long b=Lucas(n+temp-1,temp,mod2)*e2%Mod;
            ans*=(a+b);
            ans%=Mod;
        }
        printf("Case #%d: %lld\n",Case,ans);
    }
    return 0;
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值