Curious

Curious

题意

有T组数据,n, m, k, 然后有n个数字,有k次查询,每次查询以一个x, 问\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}{gcd}{(ai, aj) == x}的个数有多少


思路:

首先暴力的话时间复杂度为{O}({n}^{2})时间肯定是过不去的,然后想这里有gcd, 而且是对整个范围进行的求,非常的像莫比乌斯反演,那就可以向这个方向考虑下,首先令f({d}) = \sum\limits_{i=1}\limits^{n}\sum\limits_{j=1}^{n}gcd({ai, aj} == d), 最后只要求这个f(d), 这个就是答案,求这个可以利用一下莫比乌斯反演,令F(x) = \sum\limits_{d | x}, F(x) = \sum\limits_{d | x}\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}{gcd}{(ai, aj) == x}, 然后想gcd(ai, aj) == x, d | x, 求这个的数量就等价于{d | ai } ^ {d | aj} 的数量,可以设h(x)为{x | ai}, 在a数组中有几个是他的倍数,这个可以预处理出来,预处理出来之后,利用莫比乌斯反演f(d) = \sum\limits_{d | x}{h(x)}^{2}\mu(\frac{x}{d}), 接下来就是求这个式子即可,求这个式子就交给代码功底了


总结:

求这种gcd问题一般都可以用往莫比乌斯反演上靠一靠,然后疯狂推式子,最后利用莫比乌斯反演求解,求解这个莫比乌斯反演可以换元求解,如果数据量不能接受暴力,dp + 换元


#include <bits/stdc++.h>
#define endl '\n'
#define IOS ios::sync_with_stdio(false);
using namespace std;

typedef long long ll;
const ll MAXN = 1e5 + 10;
ll T, n, m, k;
ll a[MAXN];
bool vis[MAXN]; //vis标记质数
ll prime[MAXN], mu[MAXN], h[MAXN], exist[MAXN], dp[MAXN];   //分别表示质数,mu, h(x), 存在的个数,dp[i]的值是答案

void Init()
{
    for (int i = 1; i <= m; ++i)
    {
        exist[i] = 0;
    }
    for (int i = 1; i <= m; ++i)
    {
        dp[i] = -1;
    }
}

void get_mu()
{
    ll cnt = 0;
    mu[1] = 1;
    for (int i = 2; i < MAXN; ++i)  //线性筛法求mu
    {
        if (!vis[i])
            vis[i] = true, prime[++cnt] = i, mu[i] = -1;
        for (int j = 1; j <= cnt && prime[j] * i < MAXN; ++j)
        {
            vis[i * prime[j]] = true;
            if (i % prime[j] == 0)
                break;
            else
                mu[i * prime[j]] = -mu[i];    
        }
    }
}

vector<ll> v[MAXN];
void get_factor()   //求每一个数的因数
{
    for (int i = 1; i < MAXN; ++i)
    {
        for (int j = 1; j <= sqrt(i); ++j)
        {
            if (i % j == 0)
            {
                v[i].push_back(j);
                if (j * j != i)
                    v[i].push_back(i / j);
            }                  
        }
    }
}


int main()
{
	IOS; cin.tie(0), cout.tie(0);
    get_mu();   //OK
    get_factor();   //OK
    cin >> T;
    while (T--)
    {
        cin >> n >> m >> k;
        
        Init();

        for (int i = 1; i <= n; ++i)
        {
            cin >> a[i];
            ++exist[a[i]];
        }

        while (k--)
        {
            ll x;
            cin >> x;
            if (dp[x] != -1)
            {
                cout << dp[x] << endl;
                continue;
            }              
            ll len = 0;
            for (int i = 1; i <= m; ++i)    //记录最大的倍数
            {
                ll X = i * x;
                
                if (i * x > m)
                    break;
                len = i;    
                for (auto it : v[i])    //考虑每一个X对h(x)的贡献,然后不断的进行+即可,转换为求倍数的形式
                {
                    h[it] += exist[X];
                }
            }
            ll ans = 0;
            for (int i = 1; i <= len; ++i)
            {
                ans += h[i] * h[i] * mu[i];
                h[i] = 0;
            }

            dp[x] = ans;

            cout << dp[x] << endl;
        }
    }
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值