哈尔滨理工大学第七届程序设计竞赛决赛(网络赛-高年级组)H

题目描述

给一个有根二叉树,可以无限次的交换任意节点的左右子树,问最少交换多少次使得该树的中序遍历的字典序最小?

输入描述:

每个测试点有仅有一组数据
每组测试数据的一行有两个整数N,M,代表有N个节点,M为根节点。
接下来N行,每行两个整数ai,bi.ai表示第i个节点的左儿子,bi表示第i个节点的右儿子.

N∈[1,5×105]
ai,bi,M∈[1,N] 当ai,bi为0时 表示空节点.

输出描述:

输出两行
第一行 为最小交换次数.
第二行 为字典序最小的中序遍历.
示例1

输入

7 4
0 0
1 3
0 0
2 5
6 7
0 0
0 0

输出

0
1 2 3 4 6 5 7

题解

树形$dp$。

这题保证了每个数字都是不一样的,所以难度极小。记录$dp[i]$表示以$i$为根的子树中序遍历第一个数字的最小值,从下往上推一发就能知道哪些节点需要交换了。

#include <bits/stdc++.h>
using namespace std;

const int maxn = 5e5 + 10;
int L[maxn], R[maxn];
int f[maxn], p[maxn];
int n, m;
int u[maxn], sz;

void dfs(int x) {
  if(L[x] == 0 && R[x] == 0) {
    p[x] = x;
    return;
  }
  if(L[x]) dfs(L[x]);
  if(R[x]) dfs(R[x]);
  if(L[x] != 0 && R[x] != 0) {
    if(p[L[x]] > p[R[x]]) {
      f[x] = 1;
      p[x] = p[R[x]];
    } else {
      p[x] = p[L[x]];
    }
  } else if(L[x] == 0) {
    if(p[R[x]] < x) {
      f[x] = 1;
      p[x] = p[R[x]];
    } else {
      p[x] = x;
    }
  } else {
    if(p[L[x]] > x) {
      f[x] = 1;
      p[x] = x;
    } else {
      p[x] = p[L[x]];
    }
  }
}

void work(int x) {
  if(L[x]) work(L[x]);
  u[sz ++] = x;
  if(R[x]) work(R[x]);
}

int main() {
  while(~scanf("%d%d", &n, &m)) {
    for(int i = 1; i <= n; i ++) {
      scanf("%d%d", &L[i], &R[i]);
      f[i] = 0;
      p[i] = 0;
    }
    dfs(m);
    int ans = 0;
    for(int i = 1; i <= n; i ++) {
      ans = ans + f[i];
      if(f[i]) swap(L[i], R[i]);
    }
    printf("%d
", ans);
    sz = 0;
    work(m);
    for(int i = 0; i < n; i ++) {
      printf("%d", u[i]);
      if(i < n - 1) printf(" ");
      else printf("
");
    }
  }
  return 0;
}