• 树的旋转「POI 2011」


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

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

    【输入格式】
    第一行n下面每行,一个数x

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

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

    【输出格式】
    一行,最少逆序对个数。

    题解

    此题输入稍微有点毒瘤啊。。。但是反正是按照dfs序给的 就边dfs边读入好了

    如果我们把所有的叶子节点从左到右编号为(1sim n),那么某个节点(x)的子树中必定含有编号连续的一段叶子节点([l,r]),也就是说叶子节点(l)到叶子节点(r)都在(x)的子树里

    方便起见我们再把叶子节点(i)的权值定义为(v_i)

    现在考虑一个非叶子节点(x) 它的左儿子是(a),右儿子是(b) 我们记(a)子树里含有([l_a, r_a])的叶子节点,(b)子树里有([l_b, r_b])的叶子节点

    我们让(f(x))等于 满足(l_ale ile r_a, l_ble jle r_b, v_i>v_j)的所有逆序对((i,j))的数量 那么如果没有交换操作的话 答案就是所有的(f(i))之和

    这个我也不知道怎么解释。。。似乎挺显然的 因为左右儿子内部的逆序对之前已经统计完了嘛

    那么如果我们交换了(a,b)的位置 (f(x))会有什么变化呢

    (f(x))就会变成满足(l_ale ile r_a, l_ble jle r_b, v_i<v_j)((i,j))的数量

    因为左右交换了 所以原来的顺序对全部变成了逆序对 逆序对全部变成了顺序对

    怎么求逆序对?

    因为此题给出的二叉树不一定是完全二叉树 所以不能用归并排序 树状数组则不方便我们快速统计上面要求的那个东西,也不好合并 所以我们用权值线段树

    我们要求的是这个:满足(l_ale ile r_a, l_ble jle r_b, v_i>v_j)的所有((i,j))的数量 这个可以在合并左儿子和右儿子的线段树时顺便统计出来

    我们依然把左儿子叫做(a),右儿子叫做(b)

    具体来说 对于权值线段树的一个节点代表的区间([l,r])(cnt1)等于(a)的线段树在([mid+1,r])区间的元素个数(就是说左儿子的子树里的叶子节点(i) 有多少个(mid+1le v_ile r)(cnt2)等于(b)的线段树在([l,mid])区间的元素个数

    那么(cnt1)的节点和(cnt2)的节点两两匹配都一定满足(l_ale ile r_a, l_ble jle r_b, v_i>v_j) 所以(f(x))加上(cnt1*cnt2)

    对于权值线段树上每个节点都算一次

    注意 这样统计一定是不重不漏的 很好理解 但是我并不知道怎么解释。。。

    反过来也同理

    因为你要么交换 要么不交换 所以答案就加上两种(f(x))中较小的那一个

    这篇题解真难写

    代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    int n, m, son[1000005][2], out[1000005];
    
    struct segtree{
    	int lc, rc, cnt;
    } tr[4000005];
    int tot;
    
    int rt[1000005];
    ll ans;
    
    //权值线段树 ↓
    
    void update(int &ind, int l, int r, int p) {
    	if (!ind) ind = ++tot;
    	tr[ind].cnt++;
    	if (l == r) return;
    	int mid = (l + r) >> 1;
    	if (p <= mid) update(tr[ind].lc, l, mid, p);
    	else update(tr[ind].rc, mid+1, r, p);
    }
    
    int query(int ind, int l, int r, int x, int y) {
    	if (x > y || !ind) return 0;
    	if (x <= l && r <= y) return tr[ind].cnt;
    	int mid = (l + r) >> 1, ret = 0;
    	if (x <= mid) ret += query(tr[ind].lc, l, mid, x, y);
    	if (mid < y) ret += query(tr[ind].rc, mid+1, r, x, y);
    	return ret;
    }
    
    //权值线段树 ↑
    
    int merge(int x, int y, ll &mn, ll &mx) {
    	if (!x) return y;
    	if (!y) return x;
    	mn += 1ll * tr[tr[x].lc].cnt * tr[tr[y].rc].cnt;
    	mx += 1ll * tr[tr[y].lc].cnt * tr[tr[x].rc].cnt;
    	tr[x].cnt += tr[y].cnt;
    	tr[x].lc = merge(tr[x].lc, tr[y].lc, mn, mx);
    	tr[x].rc = merge(tr[x].rc, tr[y].rc, mn, mx);
    	return x;
    }
    
    int dfs() {
    	int ind = ++m;
    	int val; scanf("%d", &val);
    	if (val) {
    		update(rt[ind], 1, n, val);
    	} else {
    		int lc = dfs(), rc = dfs();
    		ll mn = 0, mx = 0;
    		rt[ind] = merge(rt[lc], rt[rc], mn, mx);
    		ans += min(mn, mx);
    	}
    	return ind;
    }
    
    int main() {
    	scanf("%d", &n);
    	dfs();
    	printf("%lld
    ", ans);
    	return 0;
    }
    
  • 相关阅读:
    12: xlrd 处理Excel文件
    11: python中的轻量级定时任务调度库:schedule
    03:git常见报错解决方法
    02: git分支管理
    01:jQuery的下拉选select2插件用法
    03: pip使用
    virsh 命令行管理虚拟机
    给新建的kvm虚拟机创建网络接口
    使用 virt-install 创建虚拟机
    使用字符界面 qemu-kvm 创建虚拟机
  • 原文地址:https://www.cnblogs.com/ak-dream/p/AK_DREAM63.html
Copyright © 2020-2023  润新知