『线段树合并算法入门』

点点圈 提交于 2020-03-30 10:34:02

<更新提示>

<第一次更新>


<正文>

线段树合并

对于一类问题中,假如我们有若干棵权值线段树,它们都维护相同的值域区间\([1,n]\),我们希望能够将这些线段树对应区间的关键值进行相加,同时继续维护区间最大值/最小值等信息,这就需要用到线段树合并算法。

一般来说,我们会用如下的方式来实现线段树合并:

我们用两个指针\(p,q\)分别从两个线段树的根节点出发,同步地遍历两棵线段树,并且合并相同区间的有关信息

$1. $ 若\(p,q\)之一为空,则以非空的那个节点作为合并后的节点

\(2.\)\(p,q\)均不为空,则递归地合并\(p,q\)的左右子节点,左后删除节点\(q\),并自底向上更新关键信息,留下节点\(p\)作为合并后的节点。如果已经递归到了叶节点,直接将关键值相加即可。

\(Code:\)

inline int merge(int p,int q,int l,int r)//两个指针为p,q,当前节点维护的区间为[l,r]
{
    if ( !p || !q ) return p|q;
    if ( l == r )
    {
        cnt(p) += cnt(q);
        return p;
    }
    int mid = l+r >> 1;
    ls(p) = merge( ls(p) , ls(q) , l , mid );
    rs(p) = merge( rs(p) , rs(q) , mid+1 , r );
    updata(p);
    return p;
}

接下来,我们将通过两道例题来探讨线段树合并算法的运用。

Promotion Counting

Description

The cows have once again tried to form a startup company, failing to remember from past experience that cows make terrible managers!The cows, conveniently numbered 1…N1…N (1≤N≤100,000), organize the company as a tree, with cow 1 as the president (the root of the tree). Each cow except the president has a single manager (its "parent" in the tree). Each cow ii has a distinct proficiency rating, p(i), which describes how good she is at her job. If cow ii is an ancestor (e.g., a manager of a manager of a manager) of cow jj, then we say jj is a subordinate of ii.

Unfortunately, the cows find that it is often the case that a manager has less proficiency than several of her subordinates, in which case the manager should consider promoting some of her subordinates. Your task is to help the cows figure out when this is happening. For each cow ii in the company, please count the number of subordinates jj where p(j)>p(i).

Input Format

The first line of input contains N The next N lines of input contain the proficiency ratings p(1)…p(N) for the cows. Each is a distinct integer in the range 1…1,000,000,000 The next N-1 lines describe the manager (parent) for cows 2…N Recall that cow 1 has no manager, being the president.

Output Format

Please print N lines of output. The ith line of output should tell the number of subordinates of cow ii with higher proficiency than cow i.

Sample Input

5
804289384
846930887
681692778
714636916
957747794
1
1
2
3

Sample Output

2
0
1
0
0

解析

我们先将奶牛的能力值离散化,然后考虑从树形的底部向上更新信息。具体地说,我们对每一个奶牛建立一棵权值线段树,记录下以该奶牛为根的子树其他奶牛中的能力值,显然,叶节点的线段树只有一个元素,这是可以直接插入的,同时,叶节点奶牛的答案也一定为0.

那么我们考虑一个非叶节点奶牛,它的左右儿子合起来就构成了它以下的所有奶牛,我们就用线段树合并算法来合并它左右儿子这两个奶牛的线段树,就得到了这个奶牛为根的子树中的能力值信息。考虑如何得到这个奶牛的答案,显然,只要直接在线段树上进行区间查询即可。

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
const int N = 100020;
struct SegmentTree
{
    int ls,rs,cnt;
    #define ls(x) tree[x].ls
    #define rs(x) tree[x].rs
    #define cnt(x) tree[x].cnt
}tree[20*N];
int size;
inline int build(void)
{
    tree[++size] = (SegmentTree){0,0,0};
    return size;
}
inline void updata(int p)
{
    cnt(p) = cnt(ls(p)) + cnt(rs(p));
}
inline void modify(int p,int l,int r,int val)
{
    if ( l == r ){cnt(p)++; return;}
    int mid = l+r >> 1;
    if ( val <= mid )
    {
        if ( !ls(p) ) ls(p) = build();
        modify( ls(p) , l , mid , val );
    }
    if ( val > mid )
    {
        if ( !rs(p) ) rs(p) = build();
        modify( rs(p) , mid+1 , r , val );
    }
    updata(p);
}
inline int query(int p,int l,int r,int ql,int qr)
{
    if ( !p ) return 0;
    if ( l == ql && r == qr ) return cnt(p);
    int mid = l+r >> 1 , res = 0;
    if ( qr <= mid ) return query( ls(p) , l , mid , ql , qr );
    if ( ql > mid ) return query( rs(p) , mid+1 , r , ql , qr );
    return query( ls(p) , l , mid , ql , mid ) + query( rs(p) , mid+1 , r , mid+1 , qr );
}
inline int merge(int p,int q,int l,int r)
{
    if ( !p || !q ) return p|q;
    if ( l == r )
    {
        cnt(p) += cnt(q);
        return p;
    }
    int mid = l+r >> 1;
    ls(p) = merge( ls(p) , ls(q) , l , mid );
    rs(p) = merge( rs(p) , rs(q) , mid+1 , r );
    updata(p);
    return p;
}
int n,a[N],root[N],raw[N],val[N],Head[N*2],t,tot,ans[N];
struct edge{int ver,next;}e[N*2];
inline void insert(int x,int y)
{
    e[++t] = (edge){y,Head[x]}; Head[x] = t;
}
inline int read(void)
{
    int x = 0 , w = 0; char ch;
    while (!isdigit(ch)) w |= ch=='-' , ch = getchar();
    while (isdigit(ch)) x = x*10 + ch-48 , ch = getchar();
    return w ? -x : x;
} 
inline void input(void)
{
    n = read();
    for (int i=1;i<=n;i++)
        a[i] = read() , raw[++tot] = a[i];
    for (int i=2;i<=n;i++)
    {
        int f = read();
        insert(f,i);
    }
}
inline void discrete(void)
{
    sort( raw+1 , raw+tot+1 );
    tot = unique( raw+1 , raw+tot+1 ) - (raw+1);
    for (int i=1;i<=n;i++)
        val[i] = lower_bound( raw+1 , raw+tot+1 , a[i] ) - raw;
}
inline void dfs(int x)
{
    for (int i=Head[x];i;i=e[i].next)
    {
        int y = e[i].ver;
        root[y] = build();
        dfs(y);
        root[x] = merge(root[x],root[y],1,tot);
    }
    ans[x] = query(root[x],1,tot,val[x]+1,tot);
    modify(root[x],1,tot,val[x]);
}
int main(void)
{
    input();
    discrete();
    root[1] = build();
    dfs(1);
    for (int i=1;i<=n;i++)
        printf("%d\n",ans[i]);
    return 0;
}

Tree Rotations

Description

Byteasar the gardener is growing a rare tree called Rotatus Informatikus. It has some interesting features: The tree consists of straight branches, bifurcations and leaves. The trunk stemming from the ground is also a branch. Each branch ends with either a bifurcation or a leaf on its top end. Exactly two branches fork out from a bifurcation at the end of a branch - the left branch and the right branch. Each leaf of the tree is labelled with an integer from the range . The labels of leaves are unique. With some gardening work, a so called rotation can be performed on any bifurcation, swapping the left and right branches that fork out of it. The corona of the tree is the sequence of integers obtained by reading the leaves' labels from left to right. Byteasar is from the old town of Byteburg and, like all true Byteburgers, praises neatness and order. He wonders how neat can his tree become thanks to appropriate rotations. The neatness of a tree is measured by the number of inversions in its corona, i.e. the number of pairs(I,j), (1< = I < j < = N ) such that(Ai>Aj) in the corona(A1,A2,A3…An).

The original tree (on the left) with corona(3,1,2) has two inversions. A single rotation gives a tree (on the right) with corona(1,3,2), which has only one inversion. Each of these two trees has 5 branches. Write a program that determines the minimum number of inversions in the corona of Byteasar's tree that can be obtained by rotations.

Input Format

In the first line of the standard input there is a single integer (2< = N < = 200000) that denotes the number of leaves in Byteasar's tree. Next, the description of the tree follows. The tree is defined recursively: if there is a leaf labelled with ()(1<=P<=N) at the end of the trunk (i.e., the branch from which the tree stems), then the tree's description consists of a single line containing a single integer , if there is a bifurcation at the end of the trunk, then the tree's description consists of three parts: the first line holds a single number , then the description of the left subtree follows (as if the left branch forking out of the bifurcation was its trunk), and finally the description of the right subtree follows (as if the right branch forking out of the bifurcation was its trunk).

Output Format

In the first and only line of the standard output a single integer is to be printed: the minimum number of inversions in the corona of the input tree that can be obtained by a sequence of rotations.

Sample Input

3
0
0
3
1
2

Sample Output

1

解析

题目都已经很明显了,我们先在每一个叶节点上建立一棵权值线段树,每棵权值线段树上的唯一信息即为当前叶节点的权值。和上一题相似,我们在一个递归的过程中依次向上合并线段树,就得到了一个非叶子节点的答案信息。

考虑如何使得逆序对个数最少,显然,当前节点我们能做的改变就是交换左右儿子,而这个操作只能对这一层产生影响,对下一层就没有影响了。那么我们就要尝试计算出交换或不交换左右儿子产生的逆序对个数。

对于维护相同区间的两棵线段树,计算逆序对的操作就类似与一个分治的过程,是可以在线段树合并中顺带实现的。在合并的过程当中,我们只需要计算跨越当前值域区间中点\(mid\)的逆序对个数即可,其他逆序对会在递归地合并过程中得到统计,显然,跨过\(mid\)的逆序对个数只要将当前两个节点左右子节点交错相乘即可,这是类似于一个组合的过程,交换后的逆序对个数也类似。

那么我们在合并的过程当中每一次选择两种方案里较小的一个累加,就得到了最终的最小逆序对数。

\(Code:\)

#include <bits/stdc++.h>
using namespace std;
const int N = 200020;
struct SegmentTree
{
    int ls,rs,cnt;
    #define ls(x) tree[x].ls
    #define rs(x) tree[x].rs
    #define cnt(x) tree[x].cnt
}tree[N*30];
int n,tot,root[N],t;
long long ans,sum1,sum2;
inline int build(void)
{
    tree[++tot] = (SegmentTree){0,0,0};
    return tot;
}
inline void updata(int p)
{
	cnt(p) = cnt(ls(p)) + cnt(rs(p));
}
inline void insert(int p,int l,int r,int val)
{
	if ( l == r ){cnt(p)++; return;}
	int mid = l+r >> 1;
	if ( val <= mid )
	{
		if ( !ls(p) ) ls(p) = build();
		insert( ls(p) , l , mid , val );
	}
	if ( val > mid )
	{
		if ( !rs(p) ) rs(p) = build();
		insert( rs(p) , mid+1 , r , val );
	}
	updata(p);
}
inline int merge(int p,int q,int l,int r)
{
	if ( !p || !q ) return p|q;
	if ( l == r )
	{
		cnt(p) += cnt(q);
		return p;
	}
	sum1 += 1LL * cnt(ls(p)) * cnt(rs(q));
	sum2 += 1LL * cnt(ls(q)) * cnt(rs(p));
	int mid = l+r >> 1;
	ls(p) = merge( ls(p) , ls(q) , l , mid );
	rs(p) = merge( rs(p) , rs(q) , mid+1 , r );
	updata(p);
	return p;
}
inline int dfs(void)
{
	int id; 
	scanf("%d",&id);
	if ( id != 0 )
	{
		root[++t]	= build();
		insert( root[t] , 1 , n , id );
		return root[t];
	}
	else
	{
		int node = merge( dfs() , dfs() , 1 , n );
		ans += min(sum1,sum2);
		sum1 = sum2 = 0;
		return node;
	}
}
int main(void)
{
	scanf("%d",&n);
	dfs();
	printf("%lld\n",ans);
	return 0;	
}

<后记>

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!