欧拉函数做法
有n个青蛙,m个石头,石头围成一圈,编号是(0~m-1)
第i个青蛙可以一次走
个石头
现在求所有可以被走过的石头的编号之和
假设i号青蛙走了x次,停在t位置,有:
式子拆开我们有:
很明显,这个是一个二元一次不定方程,由裴蜀定理知道:
所以对于一个编号为t的石头,当且仅当
,
走过
因为
,故:
我们设
,有:
设f(n)为小于n,且与n互质的数之和
这里直接给出
所以上面那个贡献为
所以最终答案为:
#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号青蛙走过的石头都是
的倍数
正常容斥我们可以从
入手,但是a数组的范围过大,这样肯定Tle到爆炸
然后我们会发现
所以我们可以分解m,用m的因数去容斥,最多也就1000多个因数
欧拉函数做法里面我们知道,对于m的约数k
当且仅当
,这个k才会产生贡献
这个k的贡献为
这个是没考虑容斥的贡献
我们设置一个vis数组,记录本身的贡献次数,然后在设置一个num数组,记录总贡献次数
例如我们有:2,3都对6产生了贡献,那num[6]=2,但vis[6]=1
如果vis和num不相等,说明别的数对这个点产生了一个贡献,所以:
#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;
}
}