Codeforces Round #744 (Div. 3) E2 树状数组求正反逆序数 离散化的哈希冲突

题目

事实上,问题 E1 和 E2 没有太多共同点。您可能应该将它们视为两个独立的问题。

给定一个整数数组 a[1…n]=[a1,a2,…,an]。

让我们考虑一个空的双端队列(双端队列)。 deque 是一种数据结构,支持在开头和结尾添加元素。因此,如果双端队列中当前有元素 [3,4,4],则在开头添加元素 1 将生成序列 [1,3,4,4],将相同元素添加到末尾将生成 [ 3,4,4,1]。

数组的元素依次添加到最初为空的双端队列中,从 a1 开始,以 an 结束。在将每个元素添加到双端队列之前,您可以选择是将其添加到开头还是结尾。

例如,如果我们考虑一个数组 a=[3,7,5,5],可能的动作序列之一如下所示:

1.在deque的开头加3:deque里面有一个序列[3];
2.在deque的末尾加7:deque里面有一个序列[3,7];
3.在deque的末尾加5:deque里面有一个序列[3,7,5];
4.在deque开头加5:deque里面有一个序列[5,3,7,5];
处理整个数组后,在双端队列中找到最小可能的反转次数。

序列 d 中的反转是一对索引 (i,j),使得 i<j 和 di>dj。例如,数组 d=[5,3,7,5] 正好有两个反转 - (1,2) 和 (3,4),因为 d1=5>3=d2 和 d3=7>5=d4。

输入
第一行包含一个整数 t (1≤t≤1000)——测试用例的数量。

接下来的 2t 行包含测试用例的描述。

每个测试用例描述的第一行包含一个整数 n (1≤n≤2⋅105) — 数组大小。描述的第二行包含 n 个空格分隔的整数 ai (−109≤ai≤109) — 数组的元素。

保证所有测试用例的 n 总和不超过 2⋅105。

输出
打印 t 行,每行包含对应测试用例的答案。测试用例的答案应该是一个整数——执行所描述的算法后双端队列中可能的最小反转次数。

题解思路

树状数组求逆序数中 树状数组存的是某点出现的次数,即类似桶排序一样,再用快速求前缀和来求出大于这个部分和小于这个部分的所有数,数据是1e9
,所以我们要进行离散化,排序后去重,hash出这个数在正常数列的下标一样的存在(因为排序了。我们只需比较下标就能知道大小关系了)。

这样直接跑树状数组求逆序数的板子就行了。
因为这个数加前面就相当于求之前加入的数中有多少小于这个数
加后面则相反。
比较大小取min即可。

为什么这样求能得出最优解呢?
抖腿哥证明了参考视频

有关离散化操作的问题
一开始使用的是unordered_map,发生了hash冲突使得查询复杂度从O1变成On就TLE了第19个样例。
改成map就过了。
unordered_map 底层是hash函数 , 在 大于1e8的时候还是容易出问题的。

AC代码
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
#include <map>
#include <string>
#include <unordered_map>

using namespace std;

const  int  INF =  0x3f3f3f3f;
const int N = 200010 ;

long long  tre[ N ] ;

long long   a[ N ] ;
long long   b[ N ] ;

int n;

int lbit(int x )
{
    return x & ( -x );
}

void xg(int x , int y )
{
    for (int i = x ; i <= n ; i += lbit(i) )
        tre[i] += y;
}
long long sum(int x  )
{
    long long ans = 0 ;
    for (int i = x ; i ; i -= lbit(i) )
        ans += tre[i];
    return ans;
}

int main ()
{
    ios::sync_with_stdio(false);
    int T;
    cin >> T;
    while(T--)
    {
        map <long long , int  > h ;
        long long ans = 0 ;
        int  pit  = 1 ;
        cin >> n ;
        for (int i = 1 ; i <= n ; i++ )
        {
            cin >> a[i] ;
            b[i] = a[i]  ;
        }
        sort(b+1 , b + 1 + n  );
        for (int i = 1 ; i <= n ; i++ )
        {
            if(!h[b[i]]){
                h[b[i]] = pit;
                pit++;
            }
        }
        for (int i = 1 ; i <= pit ; i++ )
            tre[i] = 0 ;
        pit--;
        for (int i = 1 ; i <= n ; i++ )
        {
             int y = h[a[i]];
             long long  t1 =  sum(pit) - sum(y) ;
             long long  t2 =  sum(y-1);
             ans += min(t1,t2);
             xg( y , 1 );
        }
        cout<<ans<<"\n";
    }
    return 0 ;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值