欧拉函数做法
有n个青蛙,m个石头,石头围成一圈,编号是(0~m-1)
第i个青蛙可以一次走
a
i
a_i
ai个石头
现在求所有可以被走过的石头的编号之和
假设i号青蛙走了x次,停在t位置,有:
x
⋅
a
i
≡
t
(
m
o
d
m
)
x \cdot a_i \equiv t \pmod m
x⋅ai≡t(modm)
式子拆开我们有:
x
⋅
a
i
−
y
⋅
m
=
t
x \cdot a_i - y \cdot m = t
x⋅ai−y⋅m=t
很明显,这个是一个二元一次不定方程,由裴蜀定理知道:
min
(
t
)
=
gcd
(
a
i
,
m
)
\min(t)=\gcd(a_i,m)
min(t)=gcd(ai,m)
所以对于一个编号为t的石头,当且仅当
gcd
(
a
i
,
m
)
∣
t
\gcd(a_i,m)\mid t
gcd(ai,m)∣t,
a
i
a_i
ai走过
因为
gcd
(
a
i
,
m
)
∣
m
\gcd(a_i,m) \mid m
gcd(ai,m)∣m,故:
gcd
(
a
i
,
m
)
∣
gcd
(
m
,
t
)
\gcd(a_i, m) \mid \gcd(m, t)
gcd(ai,m)∣gcd(m,t)
我们设
gcd
(
m
,
t
)
=
k
\gcd(m,t)=k
gcd(m,t)=k,有:
∑ gcd ( m , t ) = k m − 1 t = k ⋅ ∑ gcd ( m k , t k ) = 1 m − 1 t k \sum_{\gcd(m,t)=k}^{m-1}t=k\cdot\sum_{\gcd(\frac{m}{k},\frac{t}{k})=1}^{m-1}\frac{t}{k} ∑gcd(m,t)=km−1t=k⋅∑gcd(km,kt)=1m−1kt
设f(n)为小于n,且与n互质的数之和
f ( n ) = ∑ g c d ( n , i ) = 1 n − 1 i f(n)=\sum_{gcd(n,i)=1}^{n-1}i f(n)=∑gcd(n,i)=1n−1i
这里直接给出 f ( n ) = n ⋅ ϕ ( n ) 2 f(n)=\frac{n\cdot\phi(n)}{2} f(n)=2n⋅ϕ(n)
所以上面那个贡献为 f ( m k ) = k ⋅ m k ⋅ ϕ ( m k ) 2 = ϕ ( m k ) 2 ⋅ m f(\frac{m}{k})=k\cdot \frac{\frac{m}{k}\cdot\phi(\frac{m}{k})}{2}=\frac{\phi(\frac{m}{k})}{2}\cdot m f(km)=k⋅2km⋅ϕ(km)=2ϕ(km)⋅m
所以最终答案为:
a n s = m 2 ⋅ ∑ k ∣ m ϕ ( m k ) ( k < m ) ans=\frac{m}{2}\cdot\sum_{k \mid m}\phi(\frac{m}{k})\quad(k < m) ans=2m⋅∑k∣mϕ(km)(k<m)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
template <typename T>
void out(T x) { cout << x << endl; }
ll fast_pow(ll a, ll b, ll p) {ll c = 1; while(b) { if(b & 1) c = c * a % p; a = a * a % p; b >>= 1;} return c;}
ll exgcd(ll a, ll b, ll &x, ll &y) { if(!b) {x = 1; y = 0; return a; } ll gcd = exgcd(b, a % b, y, x); y-= a / b * x; return gcd; }
const int N = 1e4 + 10;
ll a[N], n, m;
vector <ll> vv;
ll get_phi(ll x)
{
ll ans = x;
for(int i = 2; i * i <= x; i ++)
{
if(x % i == 0)
{
ans = ans / i * (i - 1);
while(x % i == 0)
x /= i;
}
}
if(x > 1)
ans = ans / x * (x - 1);
return ans;
}
bool jud(ll x)
{
for(auto it : vv)
if(x % it == 0)
return true;
return false;
}
int main()
{
ios::sync_with_stdio(false);
int t, td = 0;
cin >> t;
while(t --)
{
td ++;
cin >> n >> m;
vv.clear();
for(int i = 1; i <= n; i ++)
{
cin >> a[i];
a[i] = __gcd(a[i], m);
vv.push_back(a[i]);
}
sort(vv.begin(), vv.end());
vv.erase(unique(vv.begin(), vv.end()), vv.end());
ll ans = 0;
for(ll i = 1; i * i <= m; i ++)
{
if(m % i == 0)
{
if(jud(i))
ans += get_phi(m / i);
if(i == 1 || i * i == m)
continue;
if(jud(m / i))
ans += get_phi(i);
}
}
ans = ans * m / 2;
cout << "Case #" << td << ": ";
cout << ans << endl;
}
}
容斥做法
由上面的我们知道,i号青蛙走过的石头都是
gcd
(
a
i
,
m
)
\gcd(a_i,m)
gcd(ai,m)的倍数
正常容斥我们可以从
gcd
(
a
i
,
m
)
\gcd(a_i,m)
gcd(ai,m)入手,但是a数组的范围过大,这样肯定Tle到爆炸
然后我们会发现
gcd
(
a
i
,
m
)
∣
m
\gcd(a_i,m) \mid m
gcd(ai,m)∣m
所以我们可以分解m,用m的因数去容斥,最多也就1000多个因数
欧拉函数做法里面我们知道,对于m的约数k
当且仅当
gcd
(
a
i
,
m
)
∣
k
\gcd(a_i,m) \mid k
gcd(ai,m)∣k,这个k才会产生贡献
这个k的贡献为
(
m
k
−
1
)
⋅
m
k
2
⋅
k
=
m
k
−
1
2
⋅
m
\frac{(\frac{m}{k}-1)\cdot\frac{m}{k}}{2}\cdot k=\frac{\frac{m}{k}-1}{2}\cdot m
2(km−1)⋅km⋅k=2km−1⋅m
这个是没考虑容斥的贡献
我们设置一个vis数组,记录本身的贡献次数,然后在设置一个num数组,记录总贡献次数
例如我们有:2,3都对6产生了贡献,那num[6]=2,但vis[6]=1
如果vis和num不相等,说明别的数对这个点产生了一个贡献,所以:
m
k
−
1
2
⋅
m
⋅
(
v
i
s
[
i
]
−
n
u
m
[
i
]
)
\frac{\frac{m}{k}-1}{2}\cdot m \cdot (vis[i] - num[i])
2km−1⋅m⋅(vis[i]−num[i])
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
template <typename T>
void out(T x) { cout << x << endl; }
ll fast_pow(ll a, ll b, ll p) {ll c = 1; while(b) { if(b & 1) c = c * a % p; a = a * a % p; b >>= 1;} return c;}
ll exgcd(ll a, ll b, ll &x, ll &y) { if(!b) {x = 1; y = 0; return a; } ll gcd = exgcd(b, a % b, y, x); y-= a / b * x; return gcd; }
const int N = 1e4 + 10;
vector <ll> vv;
int vis[N], num[N];
ll M[N];
bool check(ll x)
{
for(auto it : vv)
if(x % it == 0)
return true;
return false;
}
int main()
{
ios::sync_with_stdio(false);
int t, td = 0;
cin >> t;
while(t --)
{
vv.clear();
td ++;
ll n, m;
cin >> n >> m;
bool flag = false;
for(int i = 1; i <= n; i ++)
{
ll x;
cin >> x;
x = __gcd(x, m);
vv.push_back(x);
if(x == 1)
flag = true;
}
cout << "Case #" << td << ": ";
if(flag)
{
cout << (m - 1) * m / 2 << endl;
continue;
}
fill(vis, vis + N, 0);
fill(num, num + N, 0);
sort(vv.begin(), vv.end());
vv.erase(unique(vv.begin(), vv.end()), vv.end());
int cnt = 0;
for(int i = 2; i * i <= m; i ++)
{
if(m % i == 0)
{
M[cnt ++] = i;
if(i * i != m)
M[cnt ++] = m / i;
}
}
sort(M, M + cnt);
for(int i = 0; i < cnt; i ++)
if(check(M[i]))
vis[i] = 1;
ll sum = 0;
for(int i = 0; i < cnt; i ++)
{
if(vis[i] != num[i])
{
sum += ((m / M[i] - 1) * m) / 2 * (vis[i] - num[i]);
for(int j = i + 1; j < cnt; j ++)//更新后面是M[i]的倍数的因数,因为M[i]对他的倍数位置产生贡献
if(M[j] % M[i] == 0)
num[j] += vis[i] - num[i];
}
}
cout << sum << endl;
}
}