HDU 5514 (Frogs) (容斥原理)

来源:互联网 发布:高性能网络编程5 编辑:程序博客网 时间:2024/05/18 03:08

Frogs

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


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

分析:题意是有n只青蛙,m块石头。每只青蛙一次跳a[i]步,当这块石头被跳过,即被占领,石头是环状分布,标号是0-m,问最后青蛙占领所有的石头的标号的总和是多少。也就是求所有青蛙跳过的石头不重复的总和是多少。

肯定是容斥原理呀,求不重复的,但是太弱了,原来没看过容斥原理,后来补了补知识点,然后消除我的各种疑问,最终来写了博客。


#include<stdio.h>#include<iostream> #include <algorithm>#include<string.h>#include<vector>#include<math.h>#include<queue>#include<set>#define LL long long#define INf 0x3f3f3f3fusing namespace std;int gcd(int a,int b){    return b==0?a:gcd(b,a%b);}  int num[10005];//储存的是m的所有因子 int time[10005];//存访问了几次int vis[10005];//存是否访问 int main(){int n,x,x1,temp;scanf("%d",&n);for(int i=1;i<=n;i++){scanf("%d%d",&x,&x1);//x代表有多少青蛙  x1代表是有多少石头 memset(time,0,sizeof(time));//刷新访问的次数 memset(vis,0,sizeof(vis)); //刷新是否访问过 int count=0;for(int j=1;j<=sqrt(x1);j++)//筛选出来所有的因子 {if(x1%j==0){num[count++]=j;if(j*j!=x1)num[count++]=x1/j;}}sort(num,num+count);//从小到大排序 for(int j=1;j<=x;j++)//先把所有的访问到的因子标记上 {scanf("%d",&temp);int t=gcd(temp,x1);//求最大公约数for(int h=0;h<count;h++){if(num[h]%t==0)vis[h]=1;//只要当前的因子是GCD的倍数 就标记下那个数字 } }vis[count-1]=0;//本身转一圈就是0 long long ans=0;for(int h=0;h<count;h++){if(vis[h]!=time[h]) {int t11=(x1-1)/num[h];//有多少个数首项 num[h]存的是首项也是公差 ans +=(long long )t11*(t11+1)/2*num[h]*(vis[h]-time[h]);//容斥原理   一开始 vis[i]-t1[i]==1//对于每个因数,如果重复计算了,减去int tt=vis[h]-time[h];//看看次数差 for(int jj=h;jj<count;jj++)if(num[jj]%num[h]==0)time[jj]+=tt; }  } printf("Case #%d: %lld\n",i,ans);}return 0;} 


 题目补充:刚开始这个题目模模糊糊的AC了,但是发现自己有一部分遗漏了,现在 补充自己刚开始有的一些想法。可能思维也不是很清晰,但是我感觉我大概把我想说的说了出来,有看到的请耐心看下,然后自己手动写几组实例,自己看下如果 有什么不足,请积极评论,共同进步,谢谢。

为什么只要石头的因子

①如果给的步数和石头的GCD是1的话,也就是说这2个数字是素数或者一个是奇数和偶数,这样它会把所有的石头都会跳一边,然后当判断的时候,1会把第一个到最后一个因子都标记为1,后边判断就不会进行了,这样输出也就是所有石头的和。

②如果这两个数字是成倍数的关系 比如3 9  或者2 个偶数(不是倍数的 关系) 10 36这样的数。 3  9 这样的倍数直接会将3的所有的倍数都标记一遍,跟GCD是1是一样的因为GCD就是它跳的本身。 像10 36这样的数,GCD是2,这样36的所有2的倍数的因子都会标记一遍,但是跳的是偶数,所有他会把2的所有倍数都跳一边,这些里面也会包括一些不是36因子的2的倍数,我慢慢讲述下我的思路。

到了2的时候他会判断是相等,肯定不相等,然后他会以2为首项,2为公差将所有2的倍数都加起来,同时标记因子中所有2的倍数,代表的是所有2的倍数已经加过一遍了,但是到了4(我说的这个例子不会到4,因为这个时候4的已经加过了,vis[4]==time[4]。

比如6 同时是2 和 3的倍数,2的时候加过一次,3的时候time[6]会变成2,这样到了6的话,vis[6]-time[6]=-1,这样ans会将所有6的倍数减去,因为这样可以减去同时是2和3的倍数,防止多加。大概的我理解这个题目的容斥原理就是这样实现的。

     ans后的那个函数是刷新后边所有6的倍数的访问次数。