题意:

有n只青蛙,有m块石头,编号为0~m-1,第i只青蛙每次可以跳步,刚开始都在0,问青蛙总共可以跳到的石头之和为多少。其中

思路:

根据裴蜀定理知,对于一个有n个点的环,每个循环节的长度为n/gcd(n, k),k为每次走的步数。所以青蛙可以达到的石头编号肯定是的倍数。相当于真正步长为

很容易就知道要容斥搞一搞,看到n这么大,就没有然后了…

看了题解知道原来容斥是可以搞的。

法一:容斥原理

要求,,的倍数之和,可以利用是m的因子这个特点。

所以处理出m的因子,然后算贡献。

首先对于m以内的的倍数,标记为1,说明这些数的倍数都有可能算进答案里。然后从小到大,如果某个数的贡献(该数的倍数之和)算入了答案,那么它的倍数应该少算一次贡献,那么就把标记减1。然后每次算的时候就是标记*贡献。贡献用等差数列求和算一下就可以了。

复杂度为

一直在想这些数的lcm要怎么处理,还计数搞了好久…然而正确的思路总是明了的思想啊…

法二:欧拉函数的应用

因为可能有多只青蛙占领同一个石头的情况,所以有消除重复的办法。

比如样例一,m=12,。可以规定每个石头i只能被步长为的青蛙占领。

2,10只能由步长为2的来占领;

3,9只能由步长为3的来占领;

4,8只能由步长为4的来占领;

6只能由步长为6的来占领。

算一下和,

2+10=2*(1+5);

3+9=3*(1+3);

4+8=4*(1+2);

6=6*1。

所以如果步长为x,可以占领的石头的编号和为x*(与互质的数之和)。

因为有结论小于n,且与n互质的所有数字的和是

所以就可以做了。

这个方法比较难想到,特别是消除重复和m/x得到的地方。这么消除重复的方式可以学习一下。

代码:

法一:

typedef long long ll;
int a[10010],b[10010];
vector<int>v;
int num[10010];
int gcd(int a,int b)
{
    if(a<b){int temp;temp=a;a=b;b=temp;}
    if(b==0)return a;
    while(a%b){int r=a%b;a=b;b=r;}
    return b;
}
ll calc(int x)
{
    if(x%2==0)return 1ll*x/2*(x+1);
    return 1ll*(x+1)/2*x;
}
int main()
{
    int t,n,m;
    scanf("%d",&t);
    for(int kase=1;kase<=t;kase++)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=1;i<=n;i++)
            b[i]=gcd(m,a[i]);
        v.clear();
        for(int i=1;i*i<=m;i++)
        {
            if(m%i==0)
            {
                v.push_back(i);
                if(i!=m/i)v.push_back(m/i);
            }
        }
        sort(v.begin(),v.end());
        memset(num,0,sizeof(num));
        int len=v.size();
        for(int i=0;i<len;i++)
        {
            for(int j=1;j<=n;j++)
            {
                if(v[i]%b[j]==0)
                    num[i]=1;
            }
        }
        /*for(int i=0;i<len;i++)
            printf("%d ",v[i]);
        printf("\n");
        for(int i=0;i<len;i++)
            printf("%d ",num[i]);
        printf("\n");*/
        ll tot=0;
        for(int i=0;i<len;i++)
        {
            if(num[i]==0)continue;
            int temp=(m-1)/v[i];
            tot+=num[i]*calc(temp)*v[i];
            for(int j=i+1;j<len;j++)
            {
                if(v[j]%v[i]==0)
                    num[j]-=num[i];
            }
            /*for(int j=0;j<len;j++)
                printf("%d ",num[j]);
            printf("\n");*/
        }
        printf("Case #%d: %lld\n",kase,tot);
    }
    return 0;
}

法二:

typedef long long ll;
int a[10010],b[10010];
vector<int>v;
int num[10010];
int gcd(int a,int b)
{
    if(a<b){int temp;temp=a;a=b;b=temp;}
    if(b==0)return a;
    while(a%b){int r=a%b;a=b;b=r;}
    return b;
}
ll euler(int n)
{
    int i;
    ll res=1ll*n;
    for(i=2;i*i<=n;i++)
    {
        if(n%i==0)
        {
            res=res/i*(i-1);
            while(n%i==0)
                n=n/i;
        }
    }
    if(n>1)res=res/n*(n-1);
    return res;
}
int main()
{
    int t,n,m;
    scanf("%d",&t);
    for(int kase=1;kase<=t;kase++)
    {
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        for(int i=1;i<=n;i++)
            b[i]=gcd(m,a[i]);
        v.clear();
        for(int i=1;i*i<=m;i++)
        {
            if(m%i==0)
            {
                v.push_back(i);
                if(i!=m/i)v.push_back(m/i);
            }
        }
        memset(num,0,sizeof(num));
        ll tot=0;
        int len=v.size();
        for(int i=1;i<=n;i++)
            for(int j=0;j<len;j++)
                if(v[j]%b[i]==0)
                    num[j]=1;
        for(int i=0;i<len;i++)
        {
            if(num[i]&&v[i]!=m)
            {
                ll temp=euler(m/v[i])*m/2;
                tot+=temp;
                //printf("%d %lld\n",v[i],temp);
            }
        }
        printf("Case #%d: %lld\n",kase,tot);
    }
    return 0;
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

上下界网络流 Previous
HDU6430 树上启发式合并/线段树合并 Next