POI - 18 - Tree Rotation (线段树合并)

POI-18-Tree Rotation

第一道线段树合并题。

这题的题意是问给定一棵树,其中有 n106 个叶子节点,允许任意次交换某个结点的左右子树。在所有交换结束后,其先序遍历序列的逆序对数的最大值。

对于一棵以 u 为根节点的子树,它的逆序对数仅由以下三种情况得以产生:

  1. 左子树内部
  2. 右子树内部
  3. 一个点在左子树,一个点在右子树

因此可以递归地来做。

首先对于每个叶子结点建立一棵权值线段树。先序遍历回溯时,合并左右子树的线段树,在合并的过程中,可以求出其交换和不交换产生的逆序对,也是递归处理的思想。若交换的逆序对大,则交换,否则不交换。

因为叶子节点共 n 个,每次 build 只用到 logn 个结点,一次你空间复杂度是 O(nlogn)

时间复杂度是 O(nlogn) ,不会证。

#include
using namespace std;
typedef long long ll;
const int N=2e5+7;
int n;
ll ans;
namespace SegmentTree
{
    struct SegTree
    {
        int l,r,c;
    };
    int s[N*9];
    int segtot,top;
    ll ans0,ans1;
    SegTree seg[N*9];
    int newnode()
    {
        if(top) return s[--top];
        else return ++segtot;
    }
    void delnode(int x)
    {
        seg[x]={0,0,0};
        s[top++]=x;
    }
    inline void push_up(int rt)
    {
        seg[rt].c=seg[seg[rt].l].c+seg[seg[rt].r].c;
    }
    void build(int &rt,int l,int r,int p)
    {
        rt=newnode();
        seg[rt].c=1;
        if(l==r) return ;
        int mid=(l+r)>>1;
        if(p<=mid) build(seg[rt].l,l,mid,p);
        else build(seg[rt].r,mid+1,r,p);
    }
    int Merge(int rt1,int rt2)
    {
        if(rt1==0||rt2==0) return rt1^rt2;
        ans0+=(ll)seg[seg[rt1].l].c*seg[seg[rt2].r].c;
        ans1+=(ll)seg[seg[rt1].r].c*seg[seg[rt2].l].c;
        seg[rt1].l=Merge(seg[rt1].l,seg[rt2].l);
        seg[rt1].r=Merge(seg[rt1].r,seg[rt2].r);
        if(seg[rt1].l==0&&seg[rt1].r==0) seg[rt1].c+=seg[rt2].c;
        push_up(rt1);
        delnode(rt2);
        return rt1;
    }
    void solve(int &rtseg)
    {
        int p,lseg,rseg;
        scanf("%d",&p);
        if(p)
        {
            build(rtseg,1,n,p);
            return ;
        }
        solve(lseg);
        solve(rseg);
        ans0=ans1=0;
        rtseg=Merge(lseg,rseg);
        ans+=min(ans0,ans1);
    }
}
int main()
{
    scanf("%d",&n);
    int rt=0;
    SegmentTree::solve(rt);
    printf("%lld\n",ans);
    return 0;
}

你可能感兴趣的:(线段树合并)