HNOI2002跳蚤--容斥原理

来源:互联网 发布:栖霞商务区 网络问政 编辑:程序博客网 时间:2024/05/21 11:12

题目描述:读入n,m两个数,假设一个合法的数列是n+1位,且前n位不大于m,第n+1位为m。对于每一个数列,跳蚤可以选取任意一个数列中的数k,往左或右走k步(可以走多次),若使用这一个数列跳蚤可以到达左边一步的位置,那么这一个数列就是可以完成任务的数列。现在需要求出可以完成任务的数列的总数。

分析:题目需要找出所有能够到达左边一步位置的方案总数,能够到达左边一步,就相当于数列中所有数的最大公约数为1(可以通过扩欧推出),那么我们就转化成这样一个问题,取n个不大于m的数(位置不同也算不同方案),使得他们和m的最大公约数为1。

想到这一步,我们可以把问题转化为:所有合法数列的总数(m个数放到n位,共有m的n次方种)减去数列的最大公约数大于1的总数。

我们可以这么做:将m质因数分解,枚举m的质因数t,那么1到m之间有m/t个t的倍数,把数列中全都是这些m/t个数的方案数减去即可。

然而这种方法是存在问题的,有可能会重复减去同一种方案(6的倍数会同时在2的倍数和3的倍数时都减),所以需要枚举m的所有质因数的组合方式,记录每种组合的元素个数为tot,乘积为tmp,若tot是奇数,那么减去m/tmp个数放到n位里的方案个数,否则加上m/tmp个数放到n位里的方案个数(共有m/tmp的n次方种),这就是大名鼎鼎的容斥原理啊!

代码还是很短的。

#include<iostream>#include<cstdio>using namespace std;typedef long long LL;int a[30],n,m;LL ans;LL pow(LL x,int y){    LL ans=1;    while(y){        if(y&1)ans*=x;        x*=x;        y>>=1;    }    return ans;}void solve(){    int k=m;    for(int i=2;i*i<=k;i++)        if(k%i==0){            a[++a[0]]=i;            while(k%i==0)k/=i;        }    if(k>1)a[++a[0]]=k;    for(LL i=1;i<(1<<a[0]);i++){        int tmp=1,tot=0;        for(int j=1;j<=a[0];j++)            if(i&(1LL<<(j-1)))                tot++,tmp*=a[j];        if(tot&1)ans-=pow(m/tmp,n);        else ans+=pow(m/tmp,n);    }}int main(){    scanf("%d%d",&n,&m);    ans=pow(m,n);    solve();    printf("%LLd\n",ans);    return 0;}


1 3
原创粉丝点击