hdu 5514 2015 icpc 沈阳现场 F Frogs

欧拉函数做法

有n个青蛙,m个石头,石头围成一圈,编号是(0~m-1)
第i个青蛙可以一次走 a i a_i 个石头
现在求所有可以被走过的石头的编号之和
假设i号青蛙走了x次,停在t位置,有:
x a i t ( m o d m ) x \cdot a_i \equiv t \pmod m
式子拆开我们有:
x a i y m = t x \cdot a_i - y \cdot m = t
很明显,这个是一个二元一次不定方程,由裴蜀定理知道:
min ( t ) = gcd ( a i , m ) \min(t)=\gcd(a_i,m)
所以对于一个编号为t的石头,当且仅当 gcd ( a i , m ) t \gcd(a_i,m)\mid t a i a_i 走过
因为 gcd ( a i , m ) m \gcd(a_i,m) \mid m ,故:
gcd ( a i , m ) gcd ( m , t ) \gcd(a_i, m) \mid \gcd(m, t)
我们设 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}

设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 ) = n ϕ ( n ) 2 f(n)=\frac{n\cdot\phi(n)}{2}

所以上面那个贡献为 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

所以最终答案为:

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)

#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 ( a i , m ) \gcd(a_i,m) 入手,但是a数组的范围过大,这样肯定Tle到爆炸
然后我们会发现 gcd ( a i , m ) m \gcd(a_i,m) \mid m
所以我们可以分解m,用m的因数去容斥,最多也就1000多个因数
欧拉函数做法里面我们知道,对于m的约数k
当且仅当 gcd ( a i , m ) k \gcd(a_i,m) \mid 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
这个是没考虑容斥的贡献
我们设置一个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])

#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;
    }
}
发布了76 篇原创文章 · 获赞 5 · 访问量 1305

猜你喜欢

转载自blog.csdn.net/qq_43101466/article/details/102886091