hdu 5514 2015 icpc 沈阳现场 F Frogs

4 篇文章 0 订阅
2 篇文章 0 订阅

欧拉函数做法

有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 xait(modm)
式子拆开我们有:
x ⋅ a i − y ⋅ m = t x \cdot a_i - y \cdot m = t xaiym=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)=km1t=kgcd(km,kt)=1m1kt

设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)=1n1i

这里直接给出 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)=k2kmϕ(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=2mkmϕ(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(km1)kmk=2km1m
这个是没考虑容斥的贡献
我们设置一个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]) 2km1m(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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值