POJ 1091 简单容斥原理(莫比乌斯反演)

跳蚤
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 8298 Accepted: 2434

Description

Z城市居住着很多只跳蚤。在Z城市周六生活频道有一个娱乐节目。一只跳蚤将被请上一个高空钢丝的正中央。钢丝很长,可以看作是无限长。节目主持人会给该跳蚤发一张卡片。卡片上写有N+1个自然数。其中最后一个是M,而前N个数都不超过M,卡片上允许有相同的数字。跳蚤每次可以从卡片上任意选择一个自然数S,然后向左,或向右跳S个单位长度。而他最终的任务是跳到距离他左边一个单位长度的地方,并捡起位于那里的礼物。
比如当N=2,M=18时,持有卡片(10, 15, 18)的跳蚤,就可以完成任务:他可以先向左跳10个单位长度,然后再连向左跳3次,每次15个单位长度,最后再向右连跳3次,每次18个单位长度。而持有卡片(12, 15, 18)的跳蚤,则怎么也不可能跳到距他左边一个单位长度的地方。
当确定N和M后,显然一共有M^N张不同的卡片。现在的问题是,在这所有的卡片中,有多少张可以完成任务。

Input

两个整数N和M(N <= 15 , M <= 100000000)。

Output

可以完成任务的卡片数。

Sample Input

2 4

Sample Output

12

Hint

这12张卡片分别是:
(1, 1, 4), (1, 2, 4), (1, 3, 4), (1, 4, 4), (2, 1, 4), (2, 3, 4),
(3, 1, 4), (3, 2, 4), (3, 3, 4), (3, 4, 4), (4, 1, 4), (4, 3, 4)

Source

HNOI 2001
题目:求a[1]*x1+a[2]*x2+...a[n]*xn+m*xn+1=1,有解时,a[1],a[2]...a[n]的不同排列个数
抽象:求gcd(a1,a2,...an,m)=1的排列数
思路:不妨定义原问题为f(m),则当gcd(a1,a2,...an,m)=d时,a[1]...a[n]的排列数,而此时它等价于gcd(a[1]/d,a[2]/d....a[n]/d,m/d)=1的排列数即为f(m/d),由莫比乌斯反演公式有:
∑u(d)∑f(m/d)=f(m)
d|m  d|m
对于一个函数f(n),记它的和函数为
∑f(d)
d|m
则反演有:
∑u(d)∑f(m/d)=f(m)
d|m  d|m

其中u(d)表示d的莫比乌斯函数值,而莫比乌斯函数为:

              1    d=1

u(d)=     (-1)^r  d=p1*p2*p3....pr(均为互不相同的素数)

               0      其他情况
所以该题的反演过程是从n=1开始,然后接下来只计算为互不相同的素数相乘得到的d的过程。
具体实现方法可以由DFS实现。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
long long ans;
long long n,m;
int cnt;
long long power(long long a,long long p)
{
    long long res=1;
    while(p)
    {
        if(p&1)
            res*=a;
        a*=a;
        p/=2;
    }
    return res;
}
long long p[100005];
void dfs(int s,long long pri,int flag)
{
  //  cout<<s<<" "<<pri<<" "<<flag<<endl;
    ans+=flag*power(m/pri,n);
    for(int i=s+1;i<cnt;i++)
    {
        dfs(i,pri*p[i],-flag);
    }
}
int main()
{
    while(cin>>n>>m)
    {
        cnt=0;
        long long temp=m;
        for(long long i=2;i*i<=temp;i++)
        {
            if(temp%i==0)
            {
                p[cnt++]=i;
                while(temp%i==0)
                temp/=i;
            }

        }
        if(temp>1)
            p[cnt++]=temp;
             ans=power(m,n);
            for(int i=0;i<cnt;i++)
                dfs(i,p[i],-1);
            cout<<ans<<endl;
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值