题目描述:读入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;
}