HDU 5514 Frogs(容斥问题)

来源:互联网 发布:淘宝服装设计模板 编辑:程序博客网 时间:2024/05/17 22:35

http://acm.hdu.edu.cn/showproblem.php?pid=5514

Frogs

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


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亚洲区沈阳站-重现赛(感谢东北大学)  

这是我学的第一道容斥题,可能讲的不清楚,srO 大牛 Orz 不要嫌我太菜


题意:现在有一个m块石头的围成的圈,现在有n只青蛙都在的0号石头上,圈上的石头编号为(0到m-1);现在第i只青蛙一次能跳

ai块石头,现在所有的青蛙都一直不停的跳下去,所有青蛙到过的石头的编号之和为多少?

思路:每只青蛙都会有一个周期,一周期跳过的总的石头数量为lcm(ai,m);  那么跳的总次数为lcm(ai,m)/ai;及ai*m/(gcd(ai,m)*ai)=

m/gcd(ai,m);  所以在这一个圈上跳了m/gcd(ai,m)次才回到原点(每次跳的点一定是平均分布在这个圈上的),那么平均一次跳几步呢,用一圈的总步数除以次数,就是平均一次跳几步:m/ (  m/gcd(ai,m)  )=gcd(ai,m);  所以另gi=gcd(ai,m)表示每次跳的步数,那么这只青蛙跳过的点的编号为0*gi,1*gi,2*gi,3*gi,,,,,,,(m/gi - 1) *gi;没有最后一项m,因为不存在编号m,有编号0;

提取gi得(1+2+3+......+m/gi-1)*gi=(1+m/gi-1)(m/gi-1)/2*gi= m/gi*(m/gi-1)/2*gi;   这个式子可以算出ai可以跳过的所有的编号的和;


现在我再次设一个变量fi表示m的第i个因子(i从零开始)

那么这个m可以被分解成多个因子fi,gi也为m的因子,

fi的用法:fi表示每次跳fi步,总共跳的编号总和可由fi求出(不包含其他的fi的步数,(及:f2中肯定是包含f6的所有编号,那么求的时候就把f6的减去,f3中也是这样,也要再次减去f6)),所以我们所说的fi能算出的编号和是不应该包括其他的fi的编号和的。

例如:3 6022 33 66
求出三个gi、

g0=2,g1=3,g2=6;

然后将60分解出他所拥有的因子,1,2,3,4,5,6,10,12,15,20,30,(不包括60本身)

然后用g0,g1,g2来计算答案应该是哪些因子求出的,

g0=2;那么因子(黑体的是需要的)       1,2,3,4,5,6,10,12,15,20,30,(用这些黑体数字能求出g0所走过的编号总和)

g1=3;                                                    1,2,3,4,5,6,10,12,15,20,30,

g2=6;                                                    1,2,3,4,5,6,10,12,15,20,30,

因为中间有许多是重复的,所以我们只计算一遍fi。(就比如12重复了3次,应该只算一次即可)

统计下来及  1 , 2, 3, 4, 5 ,610,12,15,20,30

在开一个数组表示他们的需要计算的次数p[i],(黑体的数字初始化次数为一)

我们从小到大枚举这些次数不为零的数字然后做相应的处理(然后更新p[i])。

例如我在计算2的时候,我们只能用这个式子算(LL) p[i] * f[i] * ( ( (LL) (m / f[i]-1) * m / f[i]) / 2 );       (这个式子上面推过了,只是加了一个次数,并且这里的f[i]就是上面的gi了)

用这个式子算的话就跟我们之前定义的就不一样了,我们应该计算(不包含后面的编号和的)编号和,现在我们已经计算了,那么我们就不算那些已经计算的编号和了,我们只需要将他们(那些是f[i]的倍数的数)的次数减少p[i]即可。

p[i]可能为正也可能为负数,正数说明少算了(应该加上相应的次数),负数说明多算了(应该减去相应的次数),0说明刚刚好,不用再算了。

具体模拟一下代码即可:

#include<stdio.h>#include<iostream>#include<math.h>#include<string>#include<string.h>#include<algorithm>#define LL long long#define N 11000using namespace std;int f[N],g[N],a[N],p[N];int lf;int main(){    int t,cas=1;    scanf("%d",&t);    while(t--)    {        int n,m;        scanf("%d%d",&n,&m);        for(int i=0;i<n;i++)        {            scanf("%d",&a[i]);            g[i]=__gcd(a[i],m);        }        lf=0;        int lm=(int)sqrt(m+0.0001);        for(int i=1;i<=lm;i++)            if(m%i==0)            {                f[lf++]=i;                if(i!=1&&i!=m/i) f[lf++]=m/i;            }        sort(f,f+lf);        memset(p,0,sizeof(p));        for(int i=0;i<n;i++)            for(int j=0;j<lf;j++)            {                if(f[j]%g[i]==0)                    p[j]=1;            }        LL ans=0;        for(int i=0;i<lf;i++)        {            if(p[i]==0) continue;            ans+=(LL)p[i]*f[i]*(((LL)(m/f[i]-1)*m/f[i])/2);            for(int j=i+1;j<lf;j++)            {                if(f[j]%f[i]==0)                  p[j]-=p[i];            }        }        printf("Case #%d: %lld\n",cas++,ans);    }}

我还看到了一份代码:解法是欧拉函数,有兴趣的可以看看

http://blog.csdn.net/xc19952007/article/details/49556623

我暂时还没看懂,Orz


 





原创粉丝点击