ABC 252 | D - Distinct Trio

题意

给定含有N个元素的数组A,输出满足下列条件的三元组(i,j,k)的数量。

  • 1i<j<kN
  • Ai,Aj,Ak 各不相同

分析

对于数对计数问题,常用的方法是枚举其中某一个数,然后快速计算选定该数的情况下满足条件的数对个数。由于枚举某个数的复杂度为O(N),所以一般情况下,计算选定该数后满足条件的数组对数的复杂度为O(1)O(log)。为达到快速计算的目的,通常需要预处理出某些值,预处理的手段一般有动态规划、前缀和等,此外,部分项应合并处理。
以下两种方法枚举不同的数值,对应不同的预处理方法。

方法一

根据题意,枚举k的大小,然后计算对于当前k,满足条件的(i,j,k)的组数Nk,该步骤分析用到了容斥原理。

Nk=Ck12Nai=akajNaj=akaiNai=ajakNai=ak=aj=Ck12(Nai=akaj+Naj=akai)Nai=aj+Nai=aj=akNai=ak=aj=Ck12(Nai=akaj+Naj=akai)Nai=aj

下面分别计算上式中各项的值。

  • Ck12=(k1)(k2)/2
  • Nai=akaj+Naj=akai=mp[ak](k1mp[ak]),可理解为在ak之前选一个与之相等的,有mp[ak]个,然后再选一个与ak不等的,有(k1mp[ak])个,将两个数中下标小的自动赋值给ai,下标大的自动赋值给aj
  • Nai=aj需要用dp来维护,以fi表示前i个数中任选两个且这两个数相等的方案数,那么有f(i)=f(i1)+mp[ai]mp[ai]表示前i1中与ai相等的数的个数。
  • Nai=aj=ak=Cmp[ak]2,mp[ak]表示ak之前等于ak的数的个数

方法二

首先转化题意,寻找三元组(i,j,k)(1i<j<kN)使得Ai,Aj,Ak各不相同,等价于寻找三元组(Ai,Aj,Ak)Ai<Aj<Ak。对于后者,我们可以遍历中间大小的数Aj,对于每一个确定的Aj,对应的三元组个数为NAj=Cntxx<AjCntxx>AjCntx可以通过预处理出前缀和从而O(1)得到。
对于二者的等价性可作如下理解:

  • 首先证明每一个(Ai,Aj,Ak都对应一组(i,j,k),由于所找Ai,Aj,Ak各不相同,所以可以得到三个互不相同的数组下标,按照大小顺序排好即得(i,j,k)
  • 其次证明每一个(i,j,k)对应一个(Ai,Aj,Ak),不难发现可能存在多对一的情况,举例来说,(i,j1,k)(i,j2,k)可能对应同一组(Ai,Aj,Ak),这意味着我们在遍历Aj时,对于是对整个数组进行for循环,相同的数值会被重复计算,同理考虑不同的i对应同一Ai与不同的k对应同一Ak的情况,发现按照以上算法该情况被正确计算
    故以上两种表示等价且算法正确。

注意: 计数类问题可能需要开long long。

代码

方法一

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 2e5 + 10;

int n;
ll a[N], f[N];
ll mp[N];

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);

    ll ans = 0;
    for(int i = 1; i <= n; i ++){
        int x = a[i];
        ans += (ll)(i - 1) * (i - 2) / 2;
        ans -= mp[x] * (i - 1 - mp[x]);
        ans -= f[i - 1];
        f[i] = f[i - 1] + mp[x];
        mp[x] ++;
    }
    printf("%lld\n", ans);

    return 0;
}

方法二

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>

using namespace std;

const int N = 2e5 + 10;

typedef long long ll;

int a[N];
int n;
ll cnt[N];

int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i ++){
        int x;
        scanf("%d", &x);
        a[i] = x;
        cnt[x] ++;
    }
    for(int i = 1; i < N; i ++) cnt[i] = cnt[i - 1] + cnt[i];
    ll ans = 0;
    for(int i = 1; i <= n; i ++) {
        int t = a[i];
        ans += cnt[t - 1] * (n - cnt[t]);
    }
    printf("%lld\n", ans);

    return 0;
}

posted @   小菜珠的成长之路  阅读(140)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
点击右上角即可分享
微信分享提示