hdu5514 容斥或数论

15 篇文章 0 订阅
8 篇文章 0 订阅

传送门

题意:一排编号0-m-1的石头围成一个圈,有编号1-n的青蛙每次可以跳a[i]步,问可以跳到的石头的编号和是多少

思路:由于石头是个圈,不难发现每个青蛙最少可以跳的步数差为gcd(a[i],m),也就是说一个每次跳a[i]步的青蛙能跳到gcd(a[i],m)的及其倍数的编号的石头上,我们不难想到用容斥...用枚举子集做了半天,也wa了半天...枚举子集会爆long long,所以不可取

看到网上普遍的题解用的容斥十分巧妙,每次跳的步数一定是m的因子(显然gcd(a[i],m)都是m的因子),所以开始先把m分解因子,把能跳到的步数vis设置成1(gcd(a[i],m)的倍数由于可以被gcd(a[i],m)跳到,所以也算能跳到的倍数),ans加的是这个步数的等差数列的和乘上一个标记变量(vis[i]-num[i]),每次更新这个步数的倍数的num,这样多算的会被减去

拿第一组样例为例2,3,4,6开始的vis为1,ans加过以2为首项、以2的为公差的等差数列的和,num[4]、num[6]更新成1,加过以3为首项、以3为公差的等差数列的和,num[6]更新为2,这里我们可以看出来6被多加了一次,所以到6时,ans会减去一次以6为首项、6为公差的等差数列的和(此时vis[6]-num[6]=-1)

完整代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
typedef long long LL;
LL data[10005];
LL vis[10005];
LL num[10005];
LL gcd(LL a,LL b)
{
    if(b==0) return a;
    else gcd(b,a%b);
}
LL p[100005];
int main()
{
    int t;
    scanf("%d",&t);
    int cnt=0;
    while(t--)
    {
        cnt++;
        memset(vis,0,sizeof(vis));
        memset(num,0,sizeof(num));
        LL n,m;
        scanf("%lld%lld",&n,&m);
        int k=0;
        for(int i=1;i*i<=m;i++)
        {
            if(m%i==0)
            {
                p[k++]=i;;
                if(i*i!=m)
                    p[k++]=m/i;
            }
        }
        sort(p,p+k);
        for(int i=0;i<n;i++)
        {
            scanf("%lld",&data[i]);
            data[i]=gcd(data[i],m);
            for(int j=0;j<k;j++)
                if(p[j]%data[i]==0)
                    vis[j]=1;
        }
        vis[k-1]=0;
        LL ans=0;
        for(int i=0;i<k;i++)
        {
            if(vis[i]!=num[i])
            {
                ans=ans+(p[i]+(m-1)/p[i]*p[i])*((m-1)/p[i])/2*(vis[i]-num[i]);
            }
            LL x=vis[i]-num[i];
            for(int j=i+1;j<k;j++)
            {
                if(p[j]%p[i]==0)
                    num[j]+=x;
            }
        }
        printf("Case #%d: %lld\n",cnt,ans);
    }
    return 0;
}
我们也可以用另一种方法:之所以用容斥,是因为在求时有重复的情况,如果有一种方法,能保证没有重复的情况就能避免用容斥了,我们如果规定x编号的石头只能被gcd(x,m)的步数跳到,就能避免重复的情况

仍然拿第一组样例举例,那么

{2,10}只能被步数为2的跳到,和为2*(1+5)

{3,9}只能被步数为3的跳到,和为3*(1+3)

{4,8}只能被步数为4的跳到,和为4*(1+2)

{6}只能被步数为6的跳到,和为6*1

(0不考虑了...)

以上括号里的数(也就是x/gcd(x,m))都是与m/gcd(x,m)互质的数,由于x比m小,所以其和为小于m/gcd(x,m)的与m/gcd(x,m)互质的数的和,如果我们设m/gcd(x,m)为y,括号里的和也就是y*phi(y)/2

所以我们只要找出来能跳的步数,就能轻易地求得结果了

代码:

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<vector>
using namespace std;
typedef long long LL;
LL data[10005];
bool vis[10005];
LL gcd(LL a,LL b)
{
    if(b==0) return a;
    else gcd(b,a%b);
}
LL phi(LL a)
{
    LL s=a;
    for(LL i=2;i*i<=a;i++)
    {
        if(a%i==0)
        {
            s=s-s/i;
            while(a%i==0)
                a=a/i;
        }
    }
    if(a>1)
    {
        s=s-s/a;
    }
    return s;
}
LL p[100005];
int main()
{
    int t;
    scanf("%d",&t);
    int cnt=0;
    while(t--)
    {
        cnt++;
        memset(vis,false,sizeof(vis));
        LL n,m;
        scanf("%lld%lld",&n,&m);
        int k=0;
        for(int i=1;i*i<=m;i++)
        {
            if(m%i==0)
            {
                p[k++]=i;;
                if(i*i!=m)
                    p[k++]=m/i;
            }
        }
        sort(p,p+k);
        for(int i=0;i<n;i++)
        {
            scanf("%lld",&data[i]);
            data[i]=gcd(data[i],m);
            for(int j=0;j<k;j++)
                if(p[j]%data[i]==0)
                    vis[j]=true;
        }
        vis[k-1]=false;
        LL ans=0;
        for(int i=0;i<k;i++)
        {
            if(vis[i]==true)
            {
                LL temp=m/p[i];
                ans=ans+p[i]*temp*phi(temp)/2;
            }
        }
        printf("Case #%d: %lld\n",cnt,ans);
    }
    return 0;
}

orz...两种方法真的都十分巧妙...

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值