HDU 5514 Frogs (容斥原理)(第一篇博客~)

本文介绍了一道关于青蛙跳圈的算法题目,通过分析青蛙在圆形路径上的跳跃规律,运用数学方法特别是容斥原理,有效地解决了如何计算被青蛙占据的所有石头编号之和的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Frogs

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


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
  
3 2 12 9 10 3 60 22 33 66 9 96 81 40 48 32 64 16 96 42 72
 

Sample Output
  
Case #1: 42 Case #2: 1170 Case #3: 1872
 

Source





初次写,尝试一下,总有第一次嘛!



题意:有n只青蛙跳圈,m块石头,石头的编号为0-m-1,每次只青蛙每次跳的步数为h步,跳无限次,当一块石头被跳过,这块石头便被占领(不重复记录),求最后跳的石头的编号的和为多少,既求最后跳过的不重复的石头编号为多少。



很显然,如果m跟n小的话,直接暴力大法好就可以解决,但是此处显然不同,所以便需要用的容斥原理。



因为青蛙是跳无限次,所以我们很容易就发现,每次青蛙跳过得编号为k*gcd(h,m){k=0,1,2,3,4,5......}而且k*gcd(h,m)一定为m的因子。所以首先我们用一个tep数组讲所有m的因子存起来。


例如,第一组测试示例: 步数为9的青蛙 每次跳过的是 9、6、3然后重复,同理步数为10的青蛙每次跳过的是10、8、6、4、2重复。


由此我们来慢慢的想,岂不就是求石头编号是gcd(h,m)的倍数即可了,现在我们计算一下每个因子所贡献的编号的和,例如3这个因子所贡献的是3 6 9,3+6+9=3(1+2+3)同理2+4+6+8+10=2(1+2+3+4+5)。很显然是一个等差数列求和公式,所以我们可以推出每个因子贡献的值为tep[i]*(m/h)*(m/h-1)/2;
tep[i]为因子大小、m/h为a1+an,m/h-1为1到n的项数。


接下来就是容斥原理了,首先对于每个青蛙而言,它能到达的位置是它每次能跳的长度gcd(h,m)的倍数。但是对于很多的GCD的时候。可能会多算,,比如2的倍数,3的倍数,此时6的倍数就被多算了。。 其实也就是上边的那个思想用容斥的办法,既然6多算了一次,剪掉一次6的倍数就好。。 



代码实现:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <vector>
#include <queue>
#include <algorithm>
#include <set>
using namespace std;
typedef long long LL;
const LL INF = 1e17+5;
const LL MAXN = 1e6+5;
const int MOD = 1e9+7;
LL tep[MAXN];//存m的所有因子 
LL num[MAXN];//记录每个因子出现的次数 
LL vis[MAXN];//标记是否出现 
int GCD(int a,int b){
	if(b==0)
	return a;
	else
	return GCD(b,a%b); 
}//递归求最大公约数 
int main(){
	int t;
	scanf("%d",&t);
	LL l=1;
	while(t--){
		LL n,m;
		int cnt=0;
		memset(num,0,sizeof(num));
		memset(vis,0,sizeof(vis));
		scanf("%lld%lld",&n,&m);
		for(int i=1;i*i<=m;i++){
			if(m%i==0){
				if(i*i!=m)
					tep[cnt++]=m/i;
				tep[cnt++]=i;
			}
		}//求出m所有因子 
		sort(tep,tep+cnt);
		for(int i=0;i<n;i++){
			LL h;
			scanf("%lld",&h);
			LL x=GCD(h,m);
			for(int j=0;j<cnt;j++){
				if(tep[j]%x==0)
					vis[j]=1;//说明这个因子的所有都可以被跳到的位置vis[j]标记为1; 
			}
		}
		vis[cnt-1]=0;
		LL ans=0;
		//容斥一波 
		for(int i=0;i<cnt-1;i++){
			if(vis[i]!=num[i]){
				ans+=tep[i]*(m/tep[i])*(m/tep[i]-1)/2*(vis[i]-num[i]);//等差数列求和 
				LL tp=vis[i]-num[i];//很巧妙的一个点利用tp每次对num数组进行更新 
				for(int j=i;j<cnt;j++) 
					if(tep[j]%tep[i]==0)
						num[j]+=tp;//每次更新tep[j]出现的次数 
			}
		}
		printf("Case #%lld: %lld\n",l++,ans);
	}
	return 0;
}



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值