HDU6053(莫比乌斯函数+容斥定理)

37 篇文章 0 订阅
3 篇文章 0 订阅

题意:
给你一个长度为n的数组a,{ a1,a2,a3...an },求一个长度也为的数组b{ b1,b2,b3...bn }存在的种类数,b有两个要求,① 1<bi<ai;b1
题解:根据题意,b中数必须都是一个数的倍数.
首先遍历a数组找出其中最小的数amin,用k遍历从1到n中的数,看数组a中是否每个数都比k的倍数大,有则 种类数* ai/k
所以种类数res为

res=k=1amini=1na[i]k

但是这样结果会出现重复,比如15这个数,既出现在3的倍数中又出现在5的倍数中.
我们用简单的容斥定理排除掉重复的情况,也就是奇数加,偶数减
简单的用图解释一下
图1
由图一来看A,B,C,出现一次,DEF出现两次,G出现3次。根据集合运算法则
|ABC|=|A|+|B|+|C||AB||BC||AC|+|ABC|

很容易就看出奇加偶减。
但是直接这样做会超时,所以要进行分段,以因数x分段,分段求累乘。

这里没有用莫比乌斯反演没用到,只用了莫比乌斯函数的数值。
莫比乌斯反演定理传送门

code:

#include<stdio.h>
#include<string.h>
#include<math.h>
#include<algorithm>
#include<iostream>
#include<string>
#include <set>
using namespace std;
#define ll long long
#define mem(a) memset(a,0,sizeof(a))
const int eps=1e-8;
const int maxn=200000;//须填写
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
ll a;
ll mu[maxn+10];
ll num[maxn+10];
bool check[maxn+10];
ll prime[maxn+10];
void mobius()
{
    mem(check);
    mu[1]=1;
    ll tot=0;
    for(ll i=2;i<maxn;i++)
    {
        if(!check[i])
        {
            prime[tot++]=i;
            mu[i]=-1;
        }
        for(int j=0;j<tot;j++)
        {
            if(i*prime[j]>maxn)
                break;
            check[i*prime[j]]=true;
            if(i%prime[j]==0)
            {
                mu[i*prime[j]]=0;
                break;
            }
            else{
                mu[i*prime[j]]=-mu[i];
            }
        }
    }
}
ll kuai(ll x,ll y)
{
    ll ans=1;
    while(y)
    {
        if(y%2==1)
            ans=ans*x%mod;
        x=(x*x)%mod;
        y=y>>1;
    }
    ans=ans%mod;
    return ans;
}
int main()
{
    mobius();
    int kase;
    scanf("%d",&kase);
    for(int ci=1;ci<=kase;ci++)
    {
        mem(num);
        int n;
        scanf("%d",&n);
        ll mi=maxn;
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&a);
            num[a]++;
            mi=a>mi?mi:a;
        }

        for(int i=1;i<maxn;i++)
            num[i]+=num[i-1];
        ll res=0;
        //cout<<"!!!!!!!!!!!!!!!"<<endl;
        for(ll i=2;i<=mi;i++)
        {
            ll temp=1;
            for(ll j=1;j*i<=100000LL;j++)
            {
                temp=(temp*kuai(j,num[j*i+i-1]-num[j*i-1])%mod)%mod;
            }
            //cout<<"BBBB!!!!!!!!!!!!!!"<<endl;
            res=(res-temp*mu[i]%mod+mod)%mod;
        }
        //cout<<"!!!!!!!!!!!!!!!"<<endl;
        printf("Case #%d: %lld\n",ci,res);
    }
    return 0;
}





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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值