当我问这个问题时,我不知道如何解决它,因为似乎可以回答我自己的问题,所以我将自己回答这个问题:/
首先,假设排序列表中的值是 1~n 之间的整数。如果没有,您可以对它们进行排序和映射。
让我们为每个排序列表构建一个段树,段树是基于值 (1~n) 构建的。在段树的每个节点中存储了这个范围内有多少个数字,我们称之为节点的值。
似乎需要 O(nlogn) 空间来存储每个段树,但我们可以简单地删除 value=0 的节点,只有当它们的值>0 时才真正分配这些节点。
所以对于一个只有一个元素的排序列表,我们只需构建一个这个值的链,所以只需要 O(logn) 内存。
int s[SZ]/*value of a node*/,
ch[SZ][2]/*a node's two children*/;
//make a seg with only node p, return in the first argument
//call with sth. like build(root,1,n,value);
void build(int& x,int l,int r,int p)
{
x=/*a new node*/; s[x]=1;
if(l==r) return;
int m=(l+r)>>1;
if(p<=m) build(ch[x][0],l,m,p);
else build(ch[x][1],m+1,r,p);
}
当我们拆分一个段树(排序列表)时,只需递归地拆分两个孩子:
//make a new node t2, split t1 to t1 and t2 so that s[t1]=k
void split(int t1,int& t2,int k)
{
t2=/*a new node*/;
int ls=s[ch[t1][0]]; //size of t1's left child
if(k>ls) split(ch[t1][1],ch[t2][1],k-ls); //split the right child of t1
else swap(ch[t1][1],ch[t2][1]); //all right child belong to t2
if(k<ls) split(ch[t1][0],ch[t2][0],k); //split the left child of t1
s[t2]=s[t1]-k; s[t1]=k;
}
当我们合并两个排序列表时,强制合并:
//merge trees t1&t2, return merged segment tree
int merge(int t1,int t2)
{
if(t1&&t2);else return t1^t2; //nothing to merge
ch[t1][0]=merge(ch[t1][0],ch[t2][0]);
ch[t1][1]=merge(ch[t1][1],ch[t2][1]);
s[t1]+=s[t2]; /*erase t2, it's useless now*/ return t1;
}
看起来很简单,不是吗?但它的总复杂度实际上是 O(nlogn)。
证明:
让我们调查一下分配的段树节点的总数。
最初我们将分配 O(nlogn) 个这样的节点(每个节点 O(logn) 个)。
对于每次拆分尝试,我们最多会分配 O(logn) 更多,因此总共也将是 O(nlogn)。原因显然是我们将递归地只拆分节点的左孩子或右孩子。
所以分配的段树节点总数最多只有O(nlogn)。
让我们考虑合并,除了'nothing to merge',每次我们调用merge时,分配的segment tree节点的总数都会减少1(t2不再有用了)。显然 'nothing to merge' 只有在它的父亲真正被合并时才会被调用,所以它们与复杂性无关。
分配的segment tree节点总数为O(nlogn),每次有用的合并都会减1,所以所有合并的总复杂度为O(nlogn)。
总结一下,我们得到了结果。
查询第k个也很简单,我们已经做到了:)
//query k-th of segment tree x[l,r]
int ask(int x,int l,int r,int k)
{
if(l==r) return l;
int ls=s[ch[x][0]]; //how many nodes in left child
int m=(l+r)>>1;
if(k>ls) return ask(ch[x][1],m+1,r,k-ls);
return ask(ch[x][0],l,m,k);
}