数论训练 {限制} [扩展gcd][组合数][容斥原理]

校内题目{限制}

不仅仅局限与题目本身。

扩展gcd

可以解决 ax+by=c 的问题,
转换为 d=gcd(a,b) , ax/d+by/d=c/d 的问题。
因为EX_GCD可以解决形如 ax+by=gcd(a,b) 的问题。
原理如下:
gcd(a,b)=gcd(b,amodb)
朴素欧几里得证明如下:
c=gcdab
>a=mcb=nc ,其中m,n为互质的正整数
r=amodb>r=aqb,qN
>r=aqb=mcqnc=(mqn)c
b=nc,r=(mqn)c ,且 n,(mqn) 互质(假设 n,mqn 不互质,则 n=xd,mqn=yd 其中 x,y,d 都是正整数,且 d>1 ,则 a=mc=(qx+y)dc,b=xdc, 这时 a,b 的最大公约数变成 dc ,与前提矛盾,所以 nmqn 一定互质.
>gcd(b,amodb)=c=gcd(a,b)
所以可得:

ax1+by1=bx2+(amodb)y2=bx2+(aabb)y2=ay2+bx2abby2;

>ax1+by1==ay2+b(x2aby2)

>x1=y2,y1=x2aby2

一直递归求解到 b==0 时即可.

各种形如 axb(modn)>ax+ny=b

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
int T,opt;
ll gcd(ll a,ll b){
    return b==0 ? a : gcd(b,a%b);
}
void exgcd(ll a,ll b,ll &x,ll &y){
    if(b==0){x=1,y=0;return ;}
    ll x0,y0;exgcd(b,a%b,x0,y0);x=y0;y=x0-(a/b)*y0;
}
int main(){
    freopen("pay.in","r",stdin);
    freopen("pay.out","w",stdout);
    scanf("%d%d",&T,&opt);
    while(T--){
        ll a,b,c;
        scanf("%I64d%I64d%I64d",&a,&b,&c);
        ll x,y,x0,y0;
        ll d=gcd(a,b);
        if(c%d){
            if(opt==1)printf("0\n");
            else printf("0 0\n");
            continue ;
        }
        a/=d,b/=d,c/=d;exgcd(a,b,x,y);
        x*=c;x=(x%b+b)%b;//求解x>=0的一组解
        y=(c-a*x)/b;y0=(y%a+a)%a;//得到x最小时y的最大值与y的最小值。
        ll cnt=(y-y0)/a+1;//等差计算解的个数。
        ll tot=(x+x+b*(cnt-1))*cnt/2+(y+y0)*cnt/2;//等差数列。。。
        printf("%I64d",cnt);
        if(opt==2)printf(" %I64d\n",tot);
        else printf("\n");
    }
    return 0;
}

组合数

各种p为质数时 (O(1)||O(logn)) 乱搞,不讲求解法了。
讲讲lucas?昨天谈过了,扩展lucas的细节。
那讲什么?组合数->不仅仅与求法。
依据数据范围求解是必备的素质,然而组合数最重要的是各种打表找规律!!!
找规律!!!
找规律!!!
不管是杨辉三角还是卡特兰数……打表看经验yy,(在NOIP范围)一定有很好的思路。
code就发一下,但重要的是如何发现规律的。
不要吝啬if的特判,稳才有高分!

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
template<class T>inline void read(T &res){
    static char ch;T flag=1;
    while((ch=getchar())<'0'||ch>'9')if(ch=='-')flag=-1;res=ch-48;
    while((ch=getchar())>='0'&&ch<='9')res=res*10+ch-48;res*=flag;

}
const long long p=1e9+7;
long long pow_mod(long long a,long long b)  {  
    long long ans=1;a%=p;  
    while(b){  
        if(b&1)ans=ans*a%p;
        b>>=1;a=a*a%p;
    }
    return ans;
}
long long fac[1000100];
void init(){
    fac[0]=1;
    for(long long i=1;i<=1000004;++i)  
        fac[i]=fac[i-1]*i%p;
}
long long C(long long n,long long m){  
    if(m>n)return 0LL;
    if(m < 0 || n < 0) return 0LL;
    return fac[n]*pow_mod(fac[m],p-2)%p*pow_mod(fac[n-m],p-2)%p;
}
long long t,n,m,opt;
int main(){
    freopen("sumcomb.in","r",stdin);
    freopen("sumcomb.out","w",stdout);
    read(t);init();
    for(register int i=1;i<=t;i++){
        read(opt),read(n),read(m);
        if(opt==2){
            if(n+1==m){
                printf("%I64d\n",0LL);
            }else{
                n++;
                long long ans=C(n,m);
                ans=(ans%p+p)%p;
                printf("%I64d\n",ans);
            }
        }else{
            if(n+1==m){
                printf("%I64d\n",0LL);
            }else{
                m=n-m;n++;
                long long ans=C(n,m);
                ans=(ans%p+p)%p;
                printf("%I64d\n",ans);
            }
        }
    }
    return 0;
}
/*
2
1 3 2
2 3 2
*/

容斥原理

说实话,本人觉得容斥原理是本人数论的短板(就像莫比乌斯反演一样),并不是其本身的难度,是其运用的灵活性让我不敢轻易在极高复杂度下使用(本人数学类复杂计算一塌糊涂,所以容斥的剪枝让我十分苦恼。),所以就贴个板子过了吧。
n个数中选择k个使得& | 的运算达到r。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const long long mod=1e9+7;
int n,K,r,T;
const int N=1e5+10,Mod=1e9+7,P=20;  
int aa[N],cnt[1<<P],fac[N],vfac[N];
int mpow(int a,int b){
    register int rt;
    for(rt=1;b;b>>=1,a=(1LL*a*a)%Mod)
        if(b&1)rt=(1LL*rt*a)%Mod;
    return rt;
}
void init(int n){
    fac[0]=1;
    for(register int i=1;i<=n;i++)
        fac[i]=1LL*fac[i-1]*i%Mod;
    vfac[n]=mpow(fac[n],Mod-2);
    for(register int i=n-1;i>=0;i--)
        vfac[i]=1LL*vfac[i+1]*(i+1)%Mod;
}
int comb(int n, int m){
    if(m>n)return 0;
    return 1LL*fac[n]*vfac[m]%Mod*vfac[n-m]%Mod;
}
void fix(int &a){
    if(a>=Mod)a-=Mod;
    if(a<0)a+=Mod;
}
void sumup(){
    for(register int i=0;i<P;i++){
        register int s=(((1<<P)-1)^(1<<i));
        for(register int ss=s;ss>0;ss=((ss-1)&s)){
            cnt[ss]+=cnt[ss|(1<<i)];
            fix(cnt[ss]);
        }
        cnt[0]+=cnt[1<<i];
        fix(cnt[0]);
    }
}
void sumdown(){
    for(register int i=0;i<P;i++){
        register int s=(((1<<P)- 1)^(1<<i));
        for(register int ss=s;ss>0;ss=((ss-1)&s)){
            cnt[ss]-=cnt[ss|(1<<i)];
            fix(cnt[ss]);
        }
        cnt[0]-=cnt[1<<i];
        fix(cnt[0]);
    }
}
int main(){
    freopen("kor.in","r",stdin);
    freopen("kor.out","w",stdout);
    scanf("%d",&T);init(1e5);
    while(T--){
        scanf("%d%d%d",&n,&K,&r);
        memset(cnt,0,sizeof(cnt));
        r=((~r)&((1<<20)-1));//&的话就不取反。
        for(register int i=1;i<=n;i++)
            scanf("%d",aa+i),aa[i]=((~aa[i])&((1<<20)-1)),cnt[aa[i]]++;
        sumup();
        for(register int s=0;s<(1<<P);s++)cnt[s]=comb(cnt[s],K);
        sumdown();
        printf("%d\n",cnt[r]);
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值