[POI2011]ROT-Tree Rotations 线段树合并|主席树 / 逆序对

题目[POI2011]ROT-Tree Rotations

【Description】

现在有一棵二叉树,所有非叶子节点都有两个孩子.在每个叶子节点上有一个权值(有\(n\)个叶子节点,满足这些权值为\(1..n\)的一个排列).可以任意交换每个非叶子节点的左右孩子.

要求进行一系列交换,使得最终所有叶子节点的权值按照中序遍历写出来,逆序对个数最少.

【Input Format】

第一行一个整数\(n\).

下面每行,一个数\(x\).

如果\(x =0\),表示这个节点非叶子节点,递归地向下读入其左孩子和右孩子的信息.

如果\(x\ne 0\),表示这个节点是叶子节点,权值为\(x\).

【Output Format】

一行,最少逆序对个数.

【Sample Input】

3
0
0
3
1
2

【Sample Output】

1

【Hint】

对于$ 30%$的数据:\(2<=n<=5000\)

对于\(100\%\)的数据:\(2<=n<=200000\)


思路

首先需要明确的是,我们可以分层对逆序对进行计算.对于一个非叶子节点,我们可以先只计算它左子树与右子树中的叶结点形成的逆序对,至于它的某一个子树中的叶结点形成的逆序对,我们可以在下一步递归计算.

其次,在上述"分层计算"的思路中,当前层节点左右子树的交换,对向上一层的逆序对计数没有影响.换句话说,对一个节点\(p\),无论\(LC[p]\)(或\(RC[p]\))的两个子树交不交换,在\(LC[p]\)与\(RC[p]\)这一层计算出的逆序对数都是一样的.

到这里,很容易设计出算法:对于每个非叶子节点,计算交换与不交换它的子树这两种情况所产生的逆序对数,取最优值累加答案就可以了.可以考虑通过线段树合并的方式,把子树里叶结点的权值维护在权值线段树中,自底向上合并,利用线段树来求逆序对.

接下来的问题就是如何利用线段树里的数据求出逆序对.考试时我采用的方法是用类似DFS序的序列记录某一棵子树中具体包括了哪些权值,再用\(O(nlogn)\)的权值线段树求逆序对的基本算法求解.实际上,可以直接在将两个(线段树上的)节点合并为一个的时候,累加计算一个(线段树上的)节点小于等于\(mid\)(线段树区间中点)的值的个数另一个(线段树上的)节点)大于等于\(mid\)的值的个数的乘积,由于在线段树上,值域是被覆盖完全的,所以累加的答案就是(题目中的树)这一层产生的逆序对数.

代码实现比较短,却涉及到对算法的本质的理解.


代码

#include<algorithm>
#include<cstdio>
using namespace std;
#define SIZE 400005
#define SEGSIZE 4000005
int n,Root[SIZE],L[SIZE],R[SIZE],weight[SIZE],Totx;
long long P1,P2,ans;
//动态开点权值线段树
struct SegTree
{
    #define Mid ((X+Y)>>1)
    int sum[SEGSIZE],LC[SEGSIZE],RC[SEGSIZE],P;
    void change(int &x,int pos,int X,int Y)
    {
        if(x==0)x=++P;
        sum[x]++;
        if(X==Y)return;
        if(Mid>=pos)change(LC[x],pos,X,Mid);
        else change(RC[x],pos,Mid+1,Y);
    }
    int Merge(int u,int v,int X,int Y)
    {
        if(u==0||v==0)return u?u:v;
        P1+=(long long)sum[RC[u]]*sum[LC[v]];
        P2+=(long long)sum[LC[u]]*sum[RC[v]];
        sum[u]=sum[u]+sum[v];
        LC[u]=Merge(LC[u],LC[v],X,Mid);
        RC[u]=Merge(RC[u],RC[v],Mid+1,Y);
        return u;
    }
}T;
//递归建图
int Read()
{
    int x,u=++Totx;
    scanf("%d",&x);
    if(x>0)weight[u]=x;
    else{ L[u]=Read(); R[u]=Read(); }
    return u;
}
//深搜 自底向上合并
void DFS(int u)
{
    if(weight[u])T.change(Root[u],weight[u],1,n);
    else
    {
        DFS(L[u]);
        DFS(R[u]);
        P1=0,P2=0;
        Root[u]=T.Merge(Root[u],Root[L[u]],1,n);
        Root[u]=T.Merge(Root[u],Root[R[u]],1,n);
        ans+=min(P1,P2);
    }
}
int main()
{
    scanf("%d",&n);
    Read();
    DFS(1);
    printf("%lld\n",ans);
    return 0;
}

附录

考场代码(\(TLE\))

#include<algorithm>
#include<cstdio>
using namespace std;
#define SIZE 400005
#define SEGSIZE 8000005
int n,Root[SIZE],weight[SIZE],L[SIZE],R[SIZE],ans;

//动态开点权值线段树
struct SegTree
{
    #define Mid ((X+Y)>>1)
    int sum[SEGSIZE],LC[SEGSIZE],RC[SEGSIZE],P;
    int query(int x,int L,int R,int X,int Y)
    {
        if(x==0||Y<L||X>R)return 0;
        if(X>=L&&Y<=R)return sum[x];
        return query(LC[x],L,R,X,Mid)+query(RC[x],L,R,Mid+1,Y);
    }
    void change(int &x,int pos,int X,int Y,int v)
    {
        if(x==0)x=++P;
        sum[x]+=v;
        if(X==Y)return;
        if(Mid>=pos)change(LC[x],pos,X,Mid,v);
        else change(RC[x],pos,Mid+1,Y,v);
    }
    int Merge(int u,int v,int X,int Y)
    {
        if(u==0||v==0)return u?u:v;
        int x=++P;
        sum[x]=sum[u]+sum[v];
        LC[x]=Merge(LC[u],LC[v],X,Mid);
        RC[x]=Merge(RC[u],RC[v],Mid+1,Y);
        return x;
    }
}T;

//递归建图 求DFS序
int Totx=0,DFx[SIZE],DFx_C,Lx[SIZE],Rx[SIZE];
int Read()
{
    int x,u=++Totx;
    scanf("%d",&x);
    Lx[u]=DFx_C+1;
    if(x>0){ weight[u]=x; DFx[++DFx_C]=x; }
    if(x==0){ L[u]=Read(); R[u]=Read(); }
    Rx[u]=DFx_C;
    return u;
}

//深搜 自底向上合并
void DFS(int u)
{
    if(weight[u])T.change(Root[u],weight[u],1,n,1);
    else
    {
        DFS(L[u]);
        DFS(R[u]);

        int P1=0,P2=0;
        for(int i=Lx[L[u]];i<=Rx[L[u]];i++)
            P1+=T.query(Root[R[u]],1,DFx[i],1,n);//O(nlogn)计算逆序对
        for(int i=Lx[R[u]];i<=Rx[R[u]];i++)
            P2+=T.query(Root[L[u]],1,DFx[i],1,n);
        ans+=min(P1,P2);

        Root[u]=T.Merge(Root[u],Root[L[u]],1,n);
        Root[u]=T.Merge(Root[u],Root[R[u]],1,n);
    }
}

int main()
{
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    scanf("%d",&n);
    Read();
    DFS(1);
    printf("%d\n",ans);
    return 0;
} 

原文地址:https://www.cnblogs.com/TaylorSwift13/p/11178388.html

时间: 2024-10-13 12:19:31

[POI2011]ROT-Tree Rotations 线段树合并|主席树 / 逆序对的相关文章

2016湖南省赛 I Tree Intersection(线段树合并,树链剖分)

2016湖南省赛 I Tree Intersection(线段树合并,树链剖分) 传送门:https://ac.nowcoder.com/acm/contest/1112/I 题意: 给你一个n个结点的树,树上每个节点有自己的颜色 问你删除第i条边后形成的两颗子树有多少个相同的颜色 题解: 树链剖分写法: 对于每一种颜色来说,如果这个颜色是在单独的一颗子树中,那么就不会对其他的边产生贡献,所以我们单独对每一种颜色对边的贡献讨论,如果这个颜色只有一个,那么就不会产生贡献,否则,他就可以在两个相同颜

归并树 划分树 可持久化线段树(主席树) 入门题 hdu 2665

如果题目给出1e5的数据范围,,以前只会用n*log(n)的方法去想 今天学了一下两三种n*n*log(n)的数据结构 他们就是大名鼎鼎的 归并树 划分树 主席树,,,, 首先来说两个问题,,区间第k大 ,,,, 这个问题的通用算法是 划分树,, 说白一点就是把快速排序的中间结果存起来, 举个栗子 原数列 4 1 8 2 6 9 5 3 7 sorted 1 2 3 4 5 6 7 8 9 ........................... qs[0] 4 1 8 2 6 9 5 3 7 q

[可持久化线段树(主席树)]

主席树 抛出问题 如题,给定N个整数构成的序列,将对于指定的闭区间查询其区间内的第K小值. 输入输出格式 输入格式: 第一行包含两个正整数N.M,分别表示序列的长度和查询的个数. 第二行包含N个整数,表示这个序列各项的数字. 接下来M行每行包含三个整数l, r, kl,r,k , 表示查询区间[l, r][l,r]内的第k小值. 输出格式: 输出包含k行,每行1个整数,依次表示每一次查询的结果 解决问题 主席树(可持久化线段树)法 于是针对这个问题,新的数据结构诞生了,也就是主席树. 主席树本名

【bzoj2789】[Poi2012]Letters 树状数组求逆序对

题目描述 给出两个长度相同且由大写英文字母组成的字符串A.B,保证A和B中每种字母出现的次数相同. 现在每次可以交换A中相邻两个字符,求最少需要交换多少次可以使得A变成B. 输入 第一行一个正整数n (2<=n<=1,000,000),表示字符串的长度. 第二行和第三行各一个长度为n的字符串,并且只包含大写英文字母. 输出 一个非负整数,表示最少的交换次数. 样例输入 3 ABC BCA 样例输出 2 题解 树状数组求逆序对 一个结论:将序列A通过交换相邻元素变换为序列B,需要的最小次数为A中

Day2:T4用树状数组求逆序对

T4: 用树状数组求逆序对 A[I]为前缀和 推导 (A[J]-A[I])/(J-I)>=M A[j]-A[I]>=M(J-I) A[J]-M*J>=A[I]-M*I B[J]>=B[I] 之后就是求逆序对的事情了 然后这里学一下用树状数组的方法 原理是:树状数组是用来求区间和的是吧 就是按权值的区间统计那么可以BIT维护...然后扫一遍 也就是计算有多少个逆序对 按权值的区间统计就是记录数的个数

蓝桥杯小朋友排队(树状数组求逆序对)

居然存在身高为0的数据... 树状数组求逆序对原理: add(h[j],1); //将身高为h[j]的数据的出现次数加1 sum(h[j]);//求i<j 且 h[i] <=h[j] 的数据出现次数之和  那么 i-sum(h[j]) 为 i > j 且 h[i] > h[j] 数据的出现次数之和 即为 逆序对数 #include"cstdio" #include"cstring" #define lowbit(i) i&(-i) u

SGU180(树状数组,逆序对,离散)

Inversions time limit per test: 0.25 sec. memory limit per test: 4096 KB input: standard output: standard There are N integers (1<=N<=65537) A1, A2,.. AN (0<=Ai<=10^9). You need to find amount of such pairs (i, j) that 1<=i<j<=N and A

poj3067Japan——树状数组查找逆序对

题目:http://poj.org/problem?id=3067 利用树状数组查找逆序对. 代码如下: #include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int t,n,m,k; long long f[1005],ans; struct N{ long long aa,bb; }edge[1000005]; bool

bzoj2212 [Poi2011]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