牛客小白月赛87 G(快速求出一个数组中gcd==1的的逆序对个数)

文章介绍了如何通过树状数组和容斥原理计算一个排列中gcd为1的逆序对数量,涉及将数组划分为gcd的倍数区间并逐个处理的方法。
摘要由CSDN通过智能技术生成

小苯的逆序对

题意:给定一个长度为n的排列,想知道排列中有多少个数满足 ( i < j ) (i<j) (i<j) ( a i > a j ) (a_i > a_j) (ai>aj) g c d ( a i , a j ) = 1 gcd(a_i,a_j)=1 gcd(ai,aj)=1 ( i , j ) (i,j) (i,j)对。
注:一个长为n的排列即为在这个数组中,1~n每个数恰好出现一次

思路:首先题目翻译过来就是,问一个排列中有多少个逆序对的 g c d gcd gcd为1,求逆序对很好求,我们都知道可以用树状数组求,那么如何快速求出来一个数组中互质的个数呢?

我们令 d p d dp_d dpd为数组中 g c d gcd gcd为d的逆序对个数,那怎么快速求出来 d p d dp_d dpd呢?

我们可以将数组中是 d d d的倍数的所有数放到一个 v e c t o r vector vector中,然后用树状数组求一遍逆序对的数量,例如,假设 d = 10 d=10 d=10,我们可以把 10 , 20 , 50 , 100 10,20,50,100 10,20,50,100放到 v e c t o r vector vector中,这些数都是 10 10 10的倍数,任意选择两个数,他们的 g c d gcd gcd一定是 10 10 10或者是 10 10 10的倍数,我们统计这些数中逆序对的个数,但是我们会发现一个问题, 50 , 100 50,100 50,100 g c d gcd gcd并不是 10 10 10,而是 50 50 50,因此我们利用容斥原理还要减去 d p k ∗ d ( k ∗ d ≤ n ) dp_{k*d}(k*d \le n) dpkd(kdn) 的个数,也就是减去 10 10 10的倍数 d p 20 , d p 30 , … , d p k ∗ 10 dp_{20},dp_{30},\dots,dp_{k*10} dp20,dp30,,dpk10的个数,这样得到的就是 g c d gcd gcd 10 10 10的逆序对个数,我们可以倒叙枚举 g c d gcd gcd,这样每求一个 d d d,它的 k ∗ d k*d kd都是计算过的,因此最后的答案就是 d p 1 dp_1 dp1

还有一些细节就可以看代码了:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <map>
#include <cmath>

using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int N = 2e5 + 10;

int n;
int tr[N],a[N];
int pos[N];
ll dp[N];

int lowbit(int x)
{
    return x&-x;
}
void add(int x,int c)
{
    for(int i=x;i<=n;i+=lowbit(i))tr[i]+=c;
}
ll sum(int x)
{
    ll cnt=0;
    for(int i=x;i>=1;i-=lowbit(i))cnt+=tr[i];
    return cnt;
}
//上述为树状数组模板
signed main()
{
    ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
    
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        pos[a[i]]=i;//记录下来a[i]在数组中的位置
    }

    for(int d=n;d>=1;d--)
    {
        vector<int>v;
        for(int kd=d;kd<=n;kd+=d)v.push_back(pos[kd]);//vector存d的倍数在数组中的位置
        sort(v.begin(),v.end());//将所有位置排序

        for(int i=0;i<=v.size()+5;i++)tr[i]=0;
        for(auto id:v)//用树状数组求逆序对的数量
        {
            //小技巧,因为每个数都是d的倍数,因此我们可以将每个数除d,这样得到的数就是1~v.size()
            //清空数组就只用清空v.size()即可
            dp[d]+=sum(v.size())-sum(a[id]/d);
            add(a[id]/d,1);
        }
        for(int kd=2*d;kd<=n;kd+=d)dp[d]-=dp[kd];//容斥原理,减去d的倍数中的逆序对的数量
        
    }
    cout<<dp[1]<<"\n";
    return 0;
}
···

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值