hdu 5514 Frogs (容斥原理)

题目链接:点击这里

题目大意:
有一个长度为m的环,其节点编号为 [ 0 , m − 1 ] [0,m-1] [0,m1] ,有 n n n 只青蛙从 0 0 0 号点开始跳,第 i i i 只青蛙可以从 p o s pos pos 跳到 ( p o s + a i ) m o d    m (pos+a_i)\mod m (pos+ai)modm ,求所有被跳过的点的编号和

题目分析:
首先发现对于第 i i i 只青蛙可以跳到的点的编号为 gcd ⁡ ( a i , m ) \gcd(a_i,m) gcd(ai,m) 的倍数
证明:设青蛙起始位置为 p o s pos pos ,需要跳 x x x 圈,跳了 y y y 步,故有等式: m x + a i y = p o s mx+a_iy=pos mx+aiy=pos 由扩展欧几里得易知 p o s pos pos 最小正整数解为 gcd ⁡ ( a i , m ) \gcd(a_i,m) gcd(ai,m)
而显然有的点会被重复跳到多次,故会有重复,考虑用容斥原理对 m m m 的每一个因子进行计算
如果 m m m 的某个因子 f a c fac fac 对于第 i i i 只青蛙的 p o s i = gcd ⁡ ( a i , m ) pos_i=\gcd(a_i,m) posi=gcd(ai,m) f a c m o d    p o s i = 0 fac\mod pos_i=0 facmodposi=0 那么fac的倍数均会被跳到,如果 m m m 的某个因子 f a c j fac_j facj 会被 m m m 的另一个因子整除那么 f a c j fac_j facj 的贡献就被重复计算了要减掉
首先对于每一个 x = gcd ⁡ ( a i , m ) x=\gcd(a_i,m) x=gcd(ai,m),如果 m m m 的一个因数 f a c m o d    x = = 0 fac\mod x==0 facmodx==0 ,那么 f a c fac fac 就会被跳到。
然后对于每一个会碰到的因数计算,当 m m m 的一个因数j的因数 i i i 被计算的时候, j j j 就会被重复计算,要减去。
故此题复杂度为 O ( n 2 ) O(n^2) O(n2) n n n m m m 的因子个数,而 1 e 9 1e9 1e9 内最多的一个数的因子个数是 1536 1536 1536 个,所以这个题目的时间复杂度也只是 1e6 级别的,不会超时(如果直接对每个 p o s i pos_i posi 进行容斥原理时间复杂度为 O ( 2 n ) O(2^n) O(2n) 直接 t t t 飞)
具体细节见代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

#define ll long long
#define inf 0x3f3f3f3f
using namespace std;

int read()
{
	int res = 0,flag = 1;
	char ch = getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch == '-') flag = -1;
		ch = getchar();
	}
	while(ch>='0' && ch<='9')
	{
		res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
		ch = getchar();
	}
	return res*flag;
}
const int maxn = 1e5+5;
int fac[maxn],cnt;
int ok[maxn];//ok[i]为第i个因子的贡献
void cal(int num)//计算因子
{
	cnt = 0;
	fac[++cnt] = 1;
	for(int i = 1;i*i <= num;i++)
	{
		if(i*i == num)
		{
			fac[++cnt] = i;
			break;
		}
		if(num%i == 0)
		{
			fac[++cnt] = i;
			fac[++cnt] = num/i;
		}
	}
	sort(fac+1,fac+cnt+1);
}

int main()
{
	int t = read(),cas = 0;
	while(t--)
	{
		int n = read(),m = read();
		cal(m);
		memset(ok,0,sizeof(ok));
		for(int i = 1;i <= n;i++)
		{
			int ai = read();
			int gcd = __gcd(ai,m);
			for(int j = 1;j < cnt;j++)
				if(fac[j]%gcd == 0) ok[j] = 1;
		}
		ll ans = 0;
		for(int i = 1;i < cnt;i++)
		{
			if(!ok[i]) continue;
			ll tmp = (m-1)/fac[i];
			ans += tmp*(tmp+1)/2*fac[i]*ok[i];
			for(int j = i+1;j <= cnt;j++)
			{
				if(fac[j]%fac[i] == 0) ok[j] -= ok[i];
			}
		}
		printf("Case #%d: %lld\n",++cas,ans);
	}
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值