HDU 5514 Frogs ACM/ICPC 2015 Shenyang(容斥原理)

来源:互联网 发布:fc2手机视频新域名 编辑:程序博客网 时间:2024/05/22 01:30

Frogs

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 2580    Accepted Submission(s): 849

Problem Description
There are m stones lying on a circle, and n frogs are jumping over them.
The stones are numbered from 0 to m1 and the frogs are numbered from 1 to n. The i-th frog can jump over exactly ai stones in a single step, which means from stone j mod m to stone (j+ai) mod m (since all stones lie on a circle).
All frogs start their jump at stone 0, then each of them can jump as many steps as he wants. A frog will occupy a stone when he reach it, and he will keep jumping to occupy as much stones as possible. A stone is still considered ``occupied" after a frog jumped away.
They would like to know which stones can be occupied by at least one of them. Since there may be too many stones, the frogs only want to know the sum of those stones' identifiers.
Input
There are multiple test cases (no more than20), and the first line contains an integer t,
meaning the total number of test cases.
For each test case, the first line contains two positive integer n and m - the number of frogs and stones respectively (1n104, 1m109).
The second line contains n integers a1,a2,,an, where ai denotes step length of the i-th frog (1ai109).
Output
For each test case, you should print first the identifier of the test case and then the sum of all occupied stones' identifiers.
Sample Input
32 129 103 6022 33 669 9681 40 48 32 64 16 96 42 72
Sample Output
Case #1: 42Case #2: 1170Case #3: 1872
Source
2015ACM/ICPC亚洲区沈阳站-重现赛(感谢东北大学)



        有很多个青蛙在绕着一个圆圈跳,第i只青蛙每次能够跳ai步,然后起始点为0,问你把所有青蛙能够踩到的点的位置编号加起来结果是多少。
        有了之前一道博弈题的经验,我们很快能够知道,如果一只青蛙每次跳的距离是ai,那么所有gcd(ai,m)的倍数的编号都能够被走到。于是就相当于求所有的gcd的倍数的和。问题很快就出现了,会出现重复。
        计算倍数的和与计算倍数的个数很类似,于是很容易想到之前多校的TrickGCD那题,那题就是用莫比乌斯函数去容斥。但是这题m的范围很大,不可能求出莫比乌斯函数,同时暴力容斥即使是O(N)的也会超时,何况还达不到O(N)。所以说这么做肯定是不行的。于是当时我就陷入了沉默,始终无法找到解决的方法。
        直到看了看题解……我们发现,其实我只需要对m的所有约数进行暴力就行了,那么这个怎么理解呢?可以证明,这样子做所有不是m约数的数字都只会被计算一次。我可以简单的证明一下,假设某个不是m约数的数字被计算了两次,那么一定是被两个互质的gcd给重复计算了,又gcd都是m的约数,那么这两个gcd的lcm要么大于m,要么还是m的约数,前者显然矛盾;后者若该数字不是lcm,那必然是lcm的倍数,则会被lcm给容斥而只计算一次。
        这样,那些不是m约数的数字我们就不用管了,每次就对m的所有约数暴力的容斥,统计每个约数应该做出的贡献次数和实际做出贡献次数,多了就减,少了就加。由于某个数字的约数个数大概是logN级别,所以总的复杂度就是O(logNlogN)。具体见代码:
#include<bits/stdc++.h>#define LL long long#define N 100010using namespace std;int num[N],times[N],tot,n,m;bool vis[N];void divide(int n)//求n的所有约数{    tot=0;    for(int i=1;i*i<=n;i++)    {        if (n%i) continue;        num[++tot]=i;        if (i*i!=n) num[++tot]=n/i;    }}int main(){    int T_T,T;    cin>>T_T;T=T_T;    while(T_T--)    {        LL ans=0;        scanf("%d%d",&n,&m);        memset(vis,0,sizeof(vis));        memset(times,0,sizeof(times));        divide(m); sort(num+1,num+1+tot);        for(int i=1;i<=n;i++)        {            int x; scanf("%d",&x);            int gcd=__gcd(x,m);            for(int i=1;i<=tot;i++)                if (num[i]%gcd==0) vis[i]=1;//统计每个点应该做的贡献        }        for(int i=1;i<=tot;i++)//进行容斥        {            int d=vis[i]-times[i];            if (vis[i]==times[i]) continue;            ans+=(LL)(m-num[i])*(m/num[i])/2*d;//计算贡献,多了就减,少了就加            for(int j=i+1;j<=tot;j++)                if (num[j]%num[i]==0) times[j]+=d;//容斥操作,所有倍数实际贡献更改        }        printf("Case #%d: %I64d\n",T-T_T,ans);    }    return 0;}

原创粉丝点击