【问题标题】:Appropriate data structure for add and find queries用于添加和查找查询的适当数据结构
【发布时间】:2019-10-31 15:57:39
【问题描述】:

我有两种类型的查询。

1 X Y

在集合中添加元素 X ,Y 次。

2 N

查询次数

X

Y

在已排序的集合中查找第 N 个元素。

我尝试了 STL 设置,但它不起作用。

我认为我们需要平衡树,每个节点包含两个数据值。

第一个值将是元素 X。另一个将是小于或等于 value 的元素的所有 Y 的前缀总和。

当我们添加元素 X 时,找到第一个值的预处理器。将与预处理器关联的第二个值添加到 Y。

当找到第 N 个元素时。在树(第二个值)中搜索立即低于 N 的值。

如何高效地实现这个数据结构?

【问题讨论】:

  • 关于std:set 的简要说明 - 每个值只能保存一份。所以你的第一个案例立即不适合它。
  • 你说你想将元素 X 添加到你的容器 Y 次。这不是 set<pair<int,int>> 所做的(对于 Y>1),但这是 multiset<int> 所做的。
  • 您使用“预处理器”这个词,我认为您的意思是“前身”。
  • 这听起来像是Indexable skip list 的完美应用程序。
  • 我们当然可以做到!但它与您给出的问题陈述不符。如果您的意思是“以某种方式将 X 与多次重复相关联”,您可以这么说。

标签: c++ algorithm data-structures


【解决方案1】:

这可以使用复杂度为 O(Q*log(10^9)) 的 segment tree 数据结构轻松完成

  1. 我们应该使用所谓的“稀疏”分段树,以便我们只在需要时创建节点,而不是创建所有节点。
  2. 在每个节点中,我们将保存 [L, R] 范围内的元素计数
  3. 现在,通过从根到叶遍历段树并更新值(还创建尚不存在的节点),可以轻松地添加一些元素 y 次。 由于段树的高度是对数的,这需要 log N 时间,其中 N 是我们的初始间隔长度 (10^9)
  4. 在段树上使用二分搜索可以轻松找到第 k 个元素,因为在每个节点上我们都知道某个范围内的元素计数,我们可以使用此信息向左或向右遍历包含 k 的元素-th

示例代码(C++):

#include <bits/stdc++.h>
using namespace std;
#define ll long long

const int sz = 31*4*5*100000;
ll seg[sz];
int L[sz],R[sz];
int nxt = 2;

void IncNode(int c, int l, int r, int idx, int val)
{
    if(l==r)
    {
        seg[c]+=val;
        return;
    }

    int m = (l+r)/2;

    if(idx <= m)
    {
        if(!L[c])L[c]=nxt++;
        IncNode(L[c],l,m,idx,val);
    }
    else
    {
        if(!R[c])R[c]=nxt++;
        IncNode(R[c],m+1,r,idx,val);
    }

    seg[c] = seg[L[c]] + seg[R[c]];
}

int FindKth(int c, int l, int r, ll k)
{
    if(l==r)return r;

    int m = (l+r)/2;

    if(seg[L[c]] >= k)return FindKth(L[c],l,m,k);
    return FindKth(R[c],m+1,r,k-seg[L[c]]);
}

int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int Q;
    cin>>Q;
    int L = 0, R = 1e9;

    while(Q--)
    {
        int type;
        cin>>type;

        if(type==1)
        {
            int x,y;
            cin>>x>>y;
            IncNode(1,L,R,x,y);
        }
        else
        {
            int k;
            cin>>k;
            cout<<FindKth(1,L,R,k)<<"\n";
        }
    }
}

【讨论】:

  • @ManoharsinhRana 是的,但我们将存储“稀疏”段树,因此在最坏的情况下,每个查询只有 log 10^9(大约 30 个)新节点
【解决方案2】:

在每个节点中维护一个前缀和是不切实际的。这意味着每次添加新节点时,都必须更新树中 every 节点中的前缀和。相反,您需要维护 子树 总和:每个节点应包含其自己的键和所有后代键的 Y 值的总和。更新树时维护子树总和应该很简单。

当您回答类型 2 的查询时,在每个节点上,如果 N 小于或等于左孩子的子树总和值 S,您将下降到左子树(我假设 N 是 1-indexed )。否则,从 N 中减去 S + 1 并下降到右子树。

顺便说一句,如果预先知道整个 X 值集,那么您可以使用范围树或二叉索引树来代替平衡 BST。

【讨论】:

  • @ManoharsinhRana 我不知道
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-05-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-08
  • 2010-10-09
  • 2017-03-16
相关资源
最近更新 更多