[BalticOI 2016 Day2]交换

牛逼题啊,完全不会,看的孔佬爷的题解,把这篇题解当成注释就好。

题目的操作很有特性,先将其给定的序列建成一颗二叉树,然后就可以直接在这上面遍历这个操作。

考虑一个点 u,设他的值为 a,假设他左儿子 u×2 值为 b,右儿子 u×2 值为 c

那么注意到,每次操作的话,从上往下进行,每次一个节点要更改值,只有可能是和它的两个儿子进行交换,就是 u 假如要变优,那么只能是由 b 或者 c 来更新他。

然后分类讨论:

1. a<min{b,c}b,c 都没 a 小,让他们靠前字典序只会增大,u 节点不交换,递归两个儿子处理儿子就好了。

2. b 为最小的,那么肯定这个时候交换 a,b,让 u 节点的值最小,为 b,这样字典序会最小,但是子节点内的顺序,现在是 a,c,但是有没有可能,会为 c,a,一定不可能,因为交换 (ls(u),u) 的时间为 u×2,交换 (rs(u),u)的时间就要为 u×2+1,因为只能先交换左节点,而此时交换右节点,会让点 u 的值变为 c,与最优字典序不符,所以子节点顺序只能为 a,c,继续递归两个儿子计算即可。

3. c 为最小的,那么肯定这个时候交换 a,c,让 u 节点的值最小,这样字典序会最小,但是子节点内的顺序,有可能是 a,b 也有可能是 b,a 这个时候两种情况都有可能,并不确定哪边会更优。

不失一般性假设 a<b,假如 a 放到左子树的最后位置为 p,那么将 b 放到左子树来,位置 p 的值一定会增大,因为我们会优先让字典序更小,也就是小的在前面,此时更小的 a 只能到位置 p,而更大的 b 自然无法到 p 以前,所以位置 p 的值一定会增大,右子树情况同理。

所以我们可以在写一个爆搜,按照我们这个策略,搜出 a 在左子树的位置 p1 和右子树的位置 p2

然后看一下 p1,p2 谁更小,就让 b 走哪边就好了。

然后爆搜写的是 (u,v,id) 表示当前在点 u,值变成 v,然后上一步来自 id 的最优能到达的位置。

然后记得在爆搜中第三种讨论一下 bv 的大小,假如是 b 小一点,就让 vb 左右子树分别走之后最终返回位置大的那一边,因为要最优化整个字典序,而不是单个字典序,b 更小,要先优化它的位置。

复杂度的话,注意到假如访问到一个点 u,爆搜中可能出现的 v 一定会是它的祖先或者某个祖先的兄弟,于是合法的 u,v 二元组只有 log 个,记忆化一下,复杂度就对了。

总复杂度,用 map 实现,就是 O(nlog2n) 的?

代码和孔佬爷的爆搜函数写的好像不大一样。

// 德丽莎你好可爱德丽莎你好可爱德丽莎你好可爱德丽莎你好可爱德丽莎你好可爱
// 德丽莎的可爱在于德丽莎很可爱,德丽莎为什么很可爱呢,这是因为德丽莎很可爱!
// 没有力量的理想是戏言,没有理想的力量是空虚
#include <bits/stdc++.h>
#define LL long long
using namespace std;
char ibuf[1 << 15], *p1, *p2;
#define getchar() (p1 == p2 && (p2 = (p1 = ibuf) + fread(ibuf, 1, 1 << 15, stdin), p1==p2) ? EOF : *p1++)
inline int read() {
  char ch = getchar();  int x = 0, f = 1;
  while (ch < '0' || ch > '9')  {  if (ch == '-')  f = -1;  ch = getchar();  }
  while (ch >= '0' && ch <= '9')  x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();
  return x * f;
}
void print(LL x) {
  if (x > 9)  print(x / 10);
  putchar(x % 10 + '0');
}
template<class T> bool chkmin(T &a, T b) { return a > b ? (a = b, true) : false; }
template<class T> bool chkmax(T &a, T b) { return a < b ? (a = b, true) : false; }
#define rep(i, l, r) for (int i = (l); i <= (r); i++)
#define repd(i, l, r) for (int i = (l); i >= (r); i--)
#define REP(i, l, r)  for (int i = (l); i < (r); i++)
const int N = 2e6;
int n, p[N];
int ls(int p) {  return p << 1;  }
int rs(int p) {  return p << 1 | 1;  }
map < pair<int,int>,int> mp;
int find(int u,int v,int id) {
  if (u > n)  return id;
  if (mp.find({u, v}) != mp.end())  return mp[{u, v}];
  int &b = p[ls(u)], &c = p[rs(u)], &w = mp[{u, v}];
  if (v < min(b, c))  return w = u;
  if (b < c)  return w = find(ls(u), v, u);
  else if (b < v) {
    int lans = find(ls(u), b, u);
    int rans = find(rs(u), b, u);
    if (lans > rans) {
      return w = find(ls(u), v, u);
    } else {
      return w = find(rs(u), v, u);
    }
  } else {
    return w = min(find(u << 1, v, u), find(u << 1 | 1, v, u));
  }
}
void work(int u) { 
  if (u > n)  return;
  int &a = p[u], &b = p[ls(u)], &c = p[rs(u)];
  if (a < min(b, c)) {
    work(ls(u));
    work(rs(u));
    return;
  }
  if (b < c) {
    swap(a, b);
    work(ls(u));
    work(rs(u));
    return;
  }
  swap(a, c);
  if (b > c)  swap(b, c);
  if (find(ls(u), b, ls(u)) > find(rs(u), b, rs(u)))  swap(b, c);
  work(ls(u));
  work(rs(u));
}
void solve() {
  n = read();
  rep (i, 1, n)  p[i] = read();
  rep (i, n + 1, 2 * n + 1)  p[i] = n + 1;
  work(1);
  rep (i, 1, n)  cout << p[i] << " ";
}
signed main () {
#ifdef LOCAL_DEFINE
  freopen("1.in", "r", stdin);
  freopen("1.ans", "w", stdout);
#endif
  int T = 1;  while (T--)  solve();
#ifdef LOCAL_DEFINE
  cerr << "Time elapsed: " << 1.0 * clock() / CLOCKS_PER_SEC << " s.\n";
#endif
  return 0;
}
posted @   Pitiless0514  阅读(105)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下
点击右上角即可分享
微信分享提示