• BZOJ.2212.[POI2011]Tree Rotations(线段树合并)


    题目链接

    (Description)

    给定一棵n个叶子的二叉树,每个叶节点有权值(1<=ai<=n)。可以任意的交换两棵子树。问最后顺序遍历树得到的叶子权值序列中,最少的逆序对数是多少。

    (Solution)

    很重要的一点是在子树内部交换左右儿子对其它子树是没有影响的。(当然更大区间内交换两棵子树对子树内部也是没有影响的)
    所以DFS,对每个节点的两棵子树,如果换了更优就换,不优就不换。
    怎么统计两棵子树换/不换产生的逆序对数呢,用两棵子树的值域线段树合并解决。换/不换产生的逆序对数根据子树的大小关系判断就行了。
    时间空间都是(O(nlog n)).

    在这里记一下我个人对线段树合并复杂度的感性证明吧...(好像就是势能分析)

    每次合并两棵树,代价是两棵树的公共节点数,设它是(x)
    在合并完两棵树后,这两棵树的(2*x)个公共节点被合并成了(x)个,相当于删掉了(x)个点。
    所以合并的代价(复杂度)就是,被合并点的点的个数,也就是删掉的点的个数。
    而要删掉这个点就要先存在这个点,初始一共有(nlog n)个节点,所以删掉点的个数不会超过(nlog n),所以总复杂度不会超过(nlog n)

    如果初始是对每个节点进行一次区间修改,和插入单点一样只会影响(log n)个点,所以初始还是一共最多有(nlog n)个点,复杂度一样。

    另外复杂度也不完全是公共节点数,因为还要从它往下一层才知道它是公共节点。
    也许是因为这个能卡一些线段树合并的复杂度吧,但是影响不大不管了

    //80628kb	7088ms
    #include <cstdio>
    #include <cctype>
    #include <algorithm>
    //#define gc() getchar()
    #define MAXIN 300000
    #define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
    typedef long long LL;
    const int N=2e5+5;
    
    int n;
    LL Ans;
    char IN[MAXIN],*SS=IN,*TT=IN;
    struct Segment_Tree
    {
    	#define S N*19//只有建树、合并的话 nlogn就够了 n(logn+1)!
    	#define lson son[x][0]
    	#define rson son[x][1]
    	int tot,sz[S],son[S][2];
    
    	void Insert(int &x,int l,int r,int p)
    	{
    		sz[x=++tot]=1;
    		if(l==r) return;
    		int m=l+r>>1;
    		if(p<=m) Insert(lson,l,m,p);
    		else Insert(rson,m+1,r,p);
    	}
    	int Merge(int x,int y,LL &ans1,LL &ans2)
    	{
    		if(!x||!y) return x^y;
    		ans1+=1ll*sz[rson]*sz[son[y][0]], ans2+=1ll*sz[lson]*sz[son[y][1]];
    		lson=Merge(lson,son[y][0],ans1,ans2);
    		rson=Merge(rson,son[y][1],ans1,ans2);
    		sz[x]+=sz[y];// sz[x]=sz[lson]+sz[rson]; 这种写法在合并叶子节点时不对啊!(y更新不了x)
    		return x;
    	}
    //	void Print(int x,int l,int r)
    //	{
    //		if(!x) return;
    //		printf("%d:%d~%d sz:%d
    ",x,l,r,sz[x]);
    //		if(l==r) ;
    //		else Print(lson,l,l+r>>1), Print(rson,(l+r>>1)+1,r);
    //	}
    }T;
    
    inline int read()
    {
    	int now=0;register char c=gc();
    	for(;!isdigit(c);c=gc());
    	for(;isdigit(c);now=now*10+c-'0',c=gc());
    	return now;
    }
    int DFS()//返回root 
    {
    	int v=read();
    	if(v) {int x; T.Insert(x,1,n,v); return x;}
    	LL ans1=0, ans2=0;
    	int rt=T.Merge(DFS(),DFS(),ans1,ans2);//当然参数顺序是反着的 
    	Ans+=std::min(ans1,ans2);
    	return rt;
    }
    
    int main()
    {
    	n=read(), DFS(), printf("%lld
    ",Ans);
    	return 0;
    }
    
  • 相关阅读:
    2018 ACM-ICPC World Finals Problem D. Gem Island(递推)
    2016-2017 ACM-ICPC Northeastern European Regional Contest Problem C. Cactus Construction(仙人掌+构造)
    JZOJ 6997. 2021.03.06【2021省赛模拟】排列(最小树形图)
    JZOJ 6653. 【2020.05.27省选模拟】树(权值线段树)
    JZOJ 6652. 【2020.05.27省选模拟】序列(贪心+序列翻转)
    JZOJ 6979. 【2021.02.03冬令营模拟】天各一方(DP)
    MyBatisPlus使用Version注解(乐观锁)
    并行库parallelStream设置并行数量
    PHPUnit漏洞复现
    使用云函数隐藏C2服务器
  • 原文地址:https://www.cnblogs.com/SovietPower/p/9300819.html
Copyright © 2020-2023  润新知