首页 > 其他分享 >Public NOIP Round #6 D 排序 题解

Public NOIP Round #6 D 排序 题解

时间:2024-11-19 16:20:03浏览次数:1  
标签:dots std right NOIP int 题解 mid 排序 Public

Description

今天是 YQH 的生日,她得到了一个 \(1\sim n\) 的排列作为礼物。
YQH 是一个有强迫症的女孩子,她希望把这个排列从小到大排序,具体的,她可以进行这样的操作:

  • 把 \([1,n]\) 分成若干个区间,假如是 \(m\) 段,依次为 \([l_1,r_1],[l_2,r_2],\dots,[l_m,r_m]\),其中 \(l_1=1,r_m=n,l_{i+1}=r_i+1,l_i\le r_i\)。

  • 假如原来的排列为 \(a_{1,\dots,n}\),那么把排列变为 \(a_{l_m},a_{l_m+1},\dots,a_{r_m},a_{l_{m-1}},a_{l_{m-1}+1},\dots,a_{r_{m-1}},\dots,a_{l_1},a_{l_1+1},\dots,a_{r_1}\),即把每一段看作一个整体,然后把这个排列进行 reverse。
    YQH 希望进行尽可能少的操作,把序列从小到大排序。但是她太笨了,所以她找到你帮忙。注意,你不需要得到最小操作数。

\(n\leq 2\times 10^4\),次数限制为 \(90\)。

Solution

考虑分治。

假设当前已经让 \(a_{[l,r]}\) 的数值域变成 \([l,r]\) 了,设 \(mid=\left\lfloor\frac{l+r}{2}\right\rfloor\),将 \(a_i\leq mid\) 视作 \(0\),否则视作 \(1\),现在需要将这个 \(01\) 序列排序,使得所有 \(0\) 都在 \(1\) 之前。

先把连续段缩掉,那么序列变为 \(10101\ldots 010\),考虑以 \(1,2,1,2\ldots\) 分段,则操作后变为 \(1000111000\ldots\),连续段数变为原来的 \(\frac{1}{3}\),所以将长度为 \(n\) 的 \(01\) 序列排序的次数为 \(O(\log_3 n)\)。

那么设 \(f(n)\) 表示将普通序列排序的次数,用上面的方式排序可以得到:

\(f(n)=f\left(\left\lceil\frac{n}{2}\right\rceil\right)+O(\log_3 n)\)

于是总次数为 \(1+\sum\limits_{j=0} \left\lceil\log_3\left(\left\lceil\frac{n}{2^j}\right\rceil\right)\right\rceil\),能过。

Code

#include <bits/stdc++.h>

// #define int int64_t

using vi = std::vector<int>;
using vvi = std::vector<std::vector<int>>;

const int kMaxN = 2e4 + 5;

int n, m;
int a[kMaxN], len[kMaxN];
bool b[kMaxN], op[kMaxN];

void prework(int l, int r) {
  int lst = l - 1;
  m = 0;
  for (int i = l; i <= r; ++i) {
    if (i == r || b[i] != b[i + 1]) {
      op[++m] = b[i], len[m] = i - lst;
      lst = i;
    }
  }
}

vvi getvec(int l, int r) {
  vvi vec;
  for (;;) {
    prework(l, r);
    if (m == 2 && op[1] == 0 && op[2] == 1) break;
    std::vector<int> v;
    for (int i = 1, now = 1; i <= m; now = 3 - now) {
      now = std::min(now, m - i + 1);
      int s = 0;
      for (int j = i; j <= i + now - 1; ++j) s += len[j];
      v.emplace_back(s);
      i += now;
    }
    vec.emplace_back(v);
    int now = l;
    std::reverse(a + l, a + 1 + r);
    std::reverse(b + l, b + 1 + r);
    std::reverse(v.begin(), v.end());
    for (auto x : v) {
      std::reverse(a + now, a + now + x);
      std::reverse(b + now, b + now + x);
      now += x;
    }
  }
  return vec;
}

vi merge(vi a, vi b) {
  vi c;
  for (auto x : a) c.emplace_back(x);
  for (auto x : b) c.emplace_back(x);
  return c;
}

vvi solve(int l, int r) {
  if (l == r) return {};
  int mid = (l + r) >> 1;
  if (r - l + 1 > 3 && (~(l + r) & 1)) ++mid;
  for (int i = l; i <= r; ++i) b[i] = (a[i] > mid);
  auto vec = getvec(l, r), L = solve(l, mid), R = solve(mid + 1, r);
  int sz = std::max(L.size(), R.size());
  if (sz & 1) ++sz;
  L.resize(sz, vi{mid - l + 1});
  R.resize(sz, vi{r - mid});
  for (int i = 0; i < sz; ++i) {
    if (~i & 1) vec.emplace_back(merge(L[i], R[i]));
    else vec.emplace_back(merge(R[i], L[i]));
  }
  return vec;
}

void dickdreamer() {
  std::cin >> n;
  for (int i = 1; i <= n; ++i) std::cin >> a[i];
  auto res = solve(1, n);
  std::cout << res.size() << '\n';
  for (auto &vec : res) {
    std::cout << vec.size() << ' ';
    for (auto x : vec) std::cout << x << ' ';
    std::cout << '\n';
  }
}

int32_t main() {
#ifdef ORZXKR
  freopen("in.txt", "r", stdin);
  freopen("out.txt", "w", stdout);
#endif
  std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
  int T = 1;
  // std::cin >> T;
  while (T--) dickdreamer();
  // std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
  return 0;
}

标签:dots,std,right,NOIP,int,题解,mid,排序,Public
From: https://www.cnblogs.com/Scarab/p/18555072

相关文章

  • 【题解】洛谷:P4805 [CCC2016] 合并饭团
    P4805[CCC2016]合并饭团希望写完这篇题解能真正地会这种题。合并两个的操作很像合并石子的操作,确实直接那么做就可以,但三个怎么办呢,暴力做法就是枚举中间两个端点然后转移,但是这样复杂度太大了有\(O(n^4)\)。于是搬出我们的双指针,在面对区间问题时双指针可以有效地解决问题,......
  • [DMY]2024 NOIP 模拟赛 Day 11
    挂分了。赛时T1看了一眼发现答案有单调性,以为是二分。想了一会发现写不成,于是去看看特殊性质,发现度数为二的性质只需要对图分一下层,记个最小值就行了。写完以后意识到正解和这个其实是一样的,只需要记录第一次到达的状态,对层数取\(\min\)再去做即可。交上去发现挂了,我的输......
  • 打卡信奥刷题(264)用C++信奥P2010[普及组/提高] [NOIP2016 普及组] 回文日期
    [NOIP2016普及组]回文日期题目背景NOIP2016普及组T2题目描述在日常生活中,通过年、月、日这三个要素可以表示出一个唯一确定的日期。牛牛习惯用888位数字表示一......
  • UOJ918 【UR #28】偷吃蛋糕 题解
    题目描述\(n\)层蛋糕,第\(i\)层大小\(c_i\),保证\(c_i\)单调不增。初始你有第\(1\)层蛋糕,然后重复以下操作,直至没有蛋糕:吃掉最大的一层蛋糕,记其大小为\(x\)。如果还有至少\(x\)层蛋糕没有给你,主办方会按编号升序给你接下来的\(x\)层蛋糕。如果只有\(y\)层蛋......
  • C -- [vs2019] C2440 错误,无法从 const char[] 转换为 char*问题解决
    https://blog.csdn.net/weixin_45525272/article/details/118699716原因新标准中,不能把指针指向一串常量解决方案一:引入[]char*str=“helloworld”;改成:charstr_tmp[]=“helloworld”;char*str=str_tmp;方案二:加constchar*str=“helloworld”;改成:......
  • ABC380 DEFG 题解
    ABC380题解赛时秒了ABCDE(好吧其实还是略有卡顿,写完大概花了55min),看到F有博弈直接跑了,没注意到数据范围这么小,看到G一下就会了大致思路,但具体细节上搞复杂了,快写完了才发现不用维护这么多(恼),然后因为某些神秘错误现在都还没有调出来。至于F,赛后看见数据范围这么小,自己独立......
  • P1314 [NOIP2011 提高组] 聪明的质监员
    题目[NOIP2011提高组]聪明的质监员题目描述小T是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有n个矿石,从1到n逐一编号,每个矿石都有自己的重量wi以及价值vi。检验矿产的流程是:给定m个区间[li,ri];选出一个参数W;对于一个区间[li,ri],计算矿石在这......
  • P1314 [NOIP2011 提高组] 聪明的质监员
    P1314[NOIP2011提高组]聪明的质监员#[NOIP2011提高组]聪明的质监员题目描述小T是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有个矿石,从到逐一编号,每个矿石都有自己的重量以及价值。检验矿产的流程是:给定个区间;选出一个参数;对于一个区间,计......
  • マス目と整数 题解
    前言题目链接:洛谷;AtCoder。题意简述给你一个\(n\timesm\)的矩形\(a\),其中已经有\(q\)个位置填上了数,你需要为剩下的位置分别填上一个非负整数,使得最终任意一个\(2\times2\)的子矩形内,左上角的数加右下角的数等于右上角的数加左下角的数,即\(\foralli\in[1,n),j\in......
  • 『模拟赛』NOIP2024加赛6
    Rank大奋场,T3没切有点菜A.草莓和前天多校T3很像,所以一眼鉴定为贪心,从大到小选比从小到大选一眼优,代价一样时横竖无所谓先后,然后sort一遍就做完了,复杂度\((n+m)\log(n+m)\)。10min切的。点击查看代码#include<bits/stdc++.h>#definefo(x,y,z)for(registerint......