P3521 [POI2011]ROT-Tree Rotations(线段树合并)

一句话题意(不用我改了.....):给一棵n(1≤n≤200000个叶子的二叉树,可以交换每个点的左右子树,要求前序遍历叶子的逆序对最少。

......这题输入很神烦呐。。。

给你一棵二叉树的dfs序(考场上没发现2333),只有叶子结点有值,然后求逆序对大小

在考场上,建树建了好久,然后暴力暴了好久,然后得到了0分的好成绩呢(我真棒)

正解其实也不难想(但是当时不会权值线段树)

题解:

其实很简单,想想:对于一层的逆序对,数量是一定的。也就是说,无论怎么转当前子树,对上一层的逆序对数量是没有影响的。

挺好理解的但是还是上张图吧:

对于第二层来说,无论左边的2,3怎么改变,相对于右边4,1或1,4的逆序对个数始终不会改变。

这个性质吼啊

于是我们只需要求块内的最小逆序对个数就行了。

然后再考虑怎么求逆序对个数。

首先,权值线段树是一个桶,而且下标是有序的(废话)

然后,两课权值线段树在本题中是等价的,也就是说,左右要合并的线段树,除了维护的区间&&存储元素不一样,是满足可并线段树的条件的。

所以,对于一个区间,逆序对只需要比较左区间的右半边(桶中)数量*右区间的左半边就行了,

然后再比较swap之后的,贪心地取下去,合并下去就行了。

.....语言表述有问题,看一下这一小段代码:

ans1+=(ll )t[t[l].rs].sum*t[t[r].ls].sum;
ans2+=(ll )t[t[l].ls].sum*t[t[r].rs].sum;

就这样,比较然后加小的,最后就是总答案了。

tips:真正明白了指针的好处,好好用啊,但是得注意一下,因为指针是直接改值,有些值不能改的话,得用一个中间变量记录。

#include<bits/stdc++.h>
using namespace std;
const int maxn=210000;
#define ll long long
struct tree
{
    int ls,rs,sum;
}t[maxn*30];
ll ans=0,ans1=0,ans2=0;
int n,pos;
int cnt=0;
void insert(int &x,int l,int r)
{
    if(!x)
    {
        x=++cnt;
    }
    t[x].sum++;
    if(l==r)
    {
        return ;
    }
    int mid=(l+r)>>1;
    if(pos<=mid)
    {
        insert(t[x].ls,l,mid);
    }
    else
    {
        insert(t[x].rs,mid+1,r);
    }
}
void merge(int &l,int r)//直接指针合并,比原来的要好写
{
    if(!l||!r)
    {
        l=l+r;
        return ;
    }
    t[l].sum+=t[r].sum;
    ans1+=(ll )t[t[l].rs].sum*t[t[r].ls].sum;
    ans2+=(ll )t[t[l].ls].sum*t[t[r].rs].sum;
    merge(t[l].ls,t[r].ls);
    merge(t[l].rs,t[r].rs);    
}
int work(int &x)
{
    int T,ls,rs;
    x=0;
    cin>>T;
    if(!T)
    {
        work(ls);
        work(rs);
        ans1=ans2=0;
        x=ls;//指针,得用中间变量存储
        merge(x,rs);
        ans+=min(ans1,ans2);
    }
    else
    {
        pos=T;
        insert(x,1,n);
    }
}
int main()
{
    scanf("%d",&n);
    int t=0;
    work(t);
    printf("%lld",ans);
    return 0;
}

(完)

 

posted @ 2019-11-01 22:16  阿基米德的澡盆  阅读(102)  评论(0编辑  收藏  举报