题目链接
题意:
有 n 个青蛙,第 i 个青蛙每次只能够跳 ai 步,现在有 m 个石头围成一圈,编号为 0 到 m−1,现在青蛙可以围着这个石头组成的圆跳无限次,每跳一次就会占领这个石头,可以无限占领,现在问你的是这 n 个青蛙占领的石头的编号的总和是多少。
思路:
先说第一种方法:
我们可以发现对于每个ai,他所能经过的石头为 k*gcd(m,ai).但是我们发现比如第一个样例
2 12
9 10 ,gcd分别为2 ,3。
2 经过 0 2 4 6 8 10
3经过 0 3 6 9
这就会有重复的,比如6就会被算两次.这就需要去掉了,这时候就可以想到这个题需要容斥一下了.那么关键是怎么容斥?
一开始我想错了,因为发现很大,我想用map存一下所有的gcd然后暴力枚举来着,再倒着容斥.然后T了.
这个题有个容斥的技巧就是预处理出所有m的因子然后进行容斥,可以发现所有被重复计算的值以及所有的gcd一定是m的因子(重复的一定是几个数的LCM).并且int范围内每个数因子的个数最多1600+。首先对于m的一个因子g,如果这个g满足题意,那么m的其他因子并且是k*g的一定被重复计算了,我们需要去掉.g对答案的贡献为 (mg−1)∗m2 (等差数列求和). 上面说的公式计算在没有重复计算的情况下的,对于重复计算的根据容斥原理,开一个数组num[i]表示m的第i个因子被加到总答案中几次.每个答案都只能被计算一次,多计算的我们就需要减掉多加的,多减的同样. 这样计算公式为: num[i]∗(mg−1)∗m2
另外对于每个gcd(ai,m) 需要将每个gcd的倍数初始置1,并且每次从小到大计算答案时,需要对第i个gcd的所有倍数的num数组进行更新.(容斥原理本质),因为上面说过因子不会很多,所以直接暴力枚举即可.
#include
#define pb push_back
using namespace std;
const int maxn = 1e4 + 5;
typedef long long ll;
int a,num[maxn];
int n,m;
vector<int>g;
int main()
{
int _;
cin>>_;
int ca = 1;
while(_--)
{
g.clear();
memset(num,0,sizeof num);
scanf("%d %d",&n,&m);
bool flag = 0;
for(int i = 1;i * i <= m;++i)
{
if(m % i == 0)
{
g.pb(i);
if(i != m/i) g.pb(m/i);
}
}
sort(g.begin(),g.end());
for(int i = 0;i < n;++i)
{
scanf("%d",&a);
a = __gcd(a,m);
if(a == 1) flag = 1;
for(int j = 0;j < g.size();++j)
if(g[j] % a == 0) num[j] = 1;
}
printf("Case #%d: ",ca++);
if(flag) {
printf("%lld\n",(ll)m*(m-1)/2);
continue;
}
ll ans = 0;
for(int i = 0;i < g.size();++i)
{
if(num[i] == 0) continue;
ans += (ll)(m/g[i] - 1) * m/2*num[i];
for(int j = i + 1;j < g.size();++j)
{
if(g[j] % g[i] == 0) num[j] -= num[i];
}
}
printf("%lld\n",ans);
}
return 0;
}
第二种做法就是欧拉函数,我觉得很巧妙。
还是和上面一样,每次都是经过k*gcd(ai,m)的格子,对于重复计算的我们规定,第i块石头只能由gcd = gcd(i,m) 的青蛙经过.(如果不存在这样的青蛙我们假设添加一个这样的青蛙,对结果的求和是没有影响的).
对于样例可以得出如下结论:
2 12
9 10
步数为2 的经过 2 10
步数为3 的经过 3 9
步数为4的经过 4 8
步数为6的经过 6 . 综上就消除了所有重复的情况.
他们每个对答案的贡献:
2*(1 +5),3*(1+3),4*(1+2),6*(1) 可以发现一个神奇的东西,对于每个x,他的贡献为 x*(与 mx 互质的所有数的和).
存在一个结论:
[1,x]中与x互质的所有的数和为 φ(x)∗x2 ,知道这个结论这个题目就简单了.
预处理m的所有因子,然后记录所有gcd(ai,m)。然后枚举每个m的因子,先判断该石头能否被走过,(即是否有gcd可以整除该因子).如果被走过就按照公式暴力计算就好了.
#include
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
int a,g[maxn];
int fac[maxn],len;
int n,m;
void init()
{
len = 0;
int tmp = m;
for(int i = 2;i*i <= m;++i)
{
if(tmp % i == 0)
{
fac[len++] = i;
if(i != tmp / i)
fac[len++] = tmp / i;
}
}
return ;
}
int phi(int x)
{
int res=x,a=x;
for(int i=2;i*i<=x;i++)
{
if(a%i==0)
{
res-=res/i;
while(a%i==0) a/=i;
}
}
if(a>1)
res-=res/a;
return res;
}
int main()
{
int _;
cin>>_;
int ca = 1;
while(_--)
{
scanf("%d %d",&n,&m);
init();
bool flag = 0;
for(int i = 0;i < n;++i)
{
scanf("%d",&a);
if(a > m) a %= m;
g[i] = __gcd(a,m);
if(g[i] == 1) flag = 1;
}
printf("Case #%d: ",ca++);
if(flag == 1)
{
printf("%lld\n",(ll)m*(m-1)/2);
continue;
}
sort(g,g+n);
n = unique(g,g+n) - g;
sort(fac,fac+len);
ll ans = 0;
for(int i = 0;i < len;++i)
{
for(int j = 0;j < n ;++j)
{
if(fac[i] % g[j]) continue;
ans += (ll)m*phi(m/fac[i])/2;
break;
}
}
printf("%lld\n",ans);
}
return 0;
}