题目[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;
}

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

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

    2016湖南省赛 I Tree Intersection(线段树合并,树链剖分) 传送门:https://ac.nowcoder.com/acm/contest/1112/I 题意: 给你一个n个结点 ...

  2. 【BZOJ3123】[SDOI2013] 森林(启发式合并主席树)

    点此看题面 大致题意: 给你一片森林,有两种操作:询问两点之间的第\(k\)小点权和在两棵树之间连一条边. 前置技能:树上主席树 做这道题目,我们首先要会树上主席树. 关于树上主席树,这有一道很好的例 ...

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

    如果题目给出1e5的数据范围,,以前只会用n*log(n)的方法去想 今天学了一下两三种n*n*log(n)的数据结构 他们就是大名鼎鼎的 归并树 划分树 主席树,,,, 首先来说两个问题,,区间第k ...

  4. bzoj 3674: 可持久化并查集加强版 (启发式合并+主席树)

    Description Description:自从zkysb出了可持久化并查集后……hzwer:乱写能AC,暴力踩标程KuribohG:我不路径压缩就过了!ndsf:暴力就可以轻松虐!zky:…… ...

  5. BZOJ 2733 [HNOI2012]永无乡 - 启发式合并主席树

    Description 1: 查询一个集合内的K大值 2: 合并两个集合 Solution 启发式合并主席树板子 Code #include<cstdio> #include<cst ...

  6. POJ 2104 K-th Number(分桶,线段树,主席树)

    一道比较经典的数据结构题.可以用多种方式来做. 一,分桶法(平方分解). 根据数字x的大小和区间内不大于x的数字数量cnt的单调性,可知第k大数kth对应的cnt应该满足cnt≥k, 且kth是满足条 ...

  7. [bzoj1901][zoj2112][Dynamic Rankings] (整体二分+树状数组 or 动态开点线段树 or 主席树)

    Dynamic Rankings Time Limit: 10 Seconds      Memory Limit: 32768 KB The Company Dynamic Rankings has ...

  8. poj 2104 K-th Number 划分树,主席树讲解

    K-th Number Input The first line of the input file contains n --- the size of the array, and m --- t ...

  9. 【题解】BZOJ3489 A Hard RMQ problem(主席树套主席树)

    [题解]A simple RMQ problem 占坑,免得咕咕咕了,争取在2h内写出代码 upd:由于博主太菜而且硬是要用指针写两个主席树,所以延后2hQAQ upd:由于博主太菜而且太懒所以他决定 ...

随机推荐

  1. [SDOI2009]晨跑[最小费用最大流]

    [SDOI2009]晨跑 最小费用最大流的板子题吧 令 \(i'=i+n\) \(i -> i'\) 建一条流量为1费用为0的边这样就不会对答案有贡献 其次是对 \(m\) 条边建 \(u'-& ...

  2. vue组件之间传值

    父组件向子组件传值 父组件可以在引用子组件时,通过属性绑定(v-bind:)的形式,把数据传递给子组件.在子组件的props中定义后即可使用数据 <div id="app"& ...

  3. css中content-box和border-box当宽度为百分比时的位置区别,vw和%区别

    盒模型 参考代码 // CSS 部分 <style> .box1,.box2{ width: 200px; height: 200px; padding: 20px; margin: 20 ...

  4. [Java]对double变量进行四舍五入,并保留小数点后位数

    1.功能 将double类型变量进行四舍五入,并保留小数点后位数 2.代码 import java.math.BigDecimal; import java.math.RoundingMode; im ...

  5. 一个简易git服务器的搭建

    查看本机ssh公钥,生成公钥 查看ssh公钥方法: 1. 打开git bash窗口 2. 进入.ssh目录: cd ~/.ssh 3. 找到id_rsa.pub文件: ls 4. 查看公钥:cat i ...

  6. 4级搭建类401-Oracle 19c Non-CDB DG搭建(Linux 主备一对一 LGWR ASYNC)公开

    项目文档引子系列是根据项目原型,制作的测试实验文档,目的是为了提升项目过程中的实际动手能力,打造精品文档AskScuti. 项目文档引子系列除特定项目目前不对外发布,仅作为博客记录,其他公开.如学员在 ...

  7. 小白月赛22 J : 计算 A + B

    J:计算 A + B 考察点 : 高精度,字符串 坑点 : 字符串中可能全是数字,或者 + 超过 1 个,需要进行特殊判断 析题得侃: 关于高精度的各种板子 Code: #include <ve ...

  8. java中拦截器与过滤器

    注:文摘自网络,仅供自己参考 1.首先要明确什么是拦截器.什么是过滤器 1.1 什么是拦截器: 拦截器,在AOP(Aspect-Oriented Programming)中用于在某个方法或字段被访问之 ...

  9. CLOUD将excel数据引入单据体

    http://club.kingdee.com/forum.php?mod=viewthread&tid=989239 http://club.kingdee.com/forum.php?mo ...

  10. 前端Css学习

    CSS 称为层叠样式表 css样式引入方式 第一种 head标签中引入 <style> /* 选择器{css属性名称:属性值;css属性名称:属性值;} */ div{ /* css注释 ...