原创文章: https://www.huilon.net.cn/article/14
Codeforces 1353D
Description
题意
给定一个长度为 n n n , 元素全为 0 0 0 的数组, 然后给定变量 i = 1 i=1 i=1. 一直执行以下操作, 直到不可操作为止:
1.选取一段数字全为
0
0
0, 长度最长的一个子段, 长度相同时, 选在整个数组最左边的一个子段
2.对于这个子段, 把最中间的元素赋值为
i
i
i, 如果长度
m
m
m 为偶数, 按
m
2
\dfrac{m}{2}
2m 作为最中间位置. 位置从
1
1
1 开始. 最后
i
i
i 自增
求最后的结果数组. 有多组测试样例
范围:
1 ≤ n ≤ 2 ⋅ 1 0 5 1≤n≤2\cdot10^5 1≤n≤2⋅105
n n n 是指所有测试样例的n加起来的数
Tutorial
题解
每次需要取长度最长, 最左边的子段, 可以考虑使用优先队列来做这道题.
普通的队列是一种先进先出的数据结构,元素在队列尾追加,从队列头删除。
.
在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先出列。优先队列具有最高优先级级先出 (first in, largest out)的行为特征。
.
取自网络, 有改动. 作者不详
我们把子段的长度和子段的左边联合作为优先队列的优先级, 长度最长, 子段最左边, 则优先级最高.
值得注意的是, 优先队列的实现方法有很多中, 在 c++ 中, 可以使用 vector, queue, set 这几个数据结构来实现, 也可以直接使用现成的 privority_queue 数据结构.
不过实践发现, privority_queue 的效率很慢, 这道题超时了. 实践发现用 set 来实现, 速度会很快. :/
用 set 来实现优先队列, 用这个重载构造:
template<_Key, _Compare = less<_Key>, _Alloc = allocator<_Key>> class set{...}
_Compare 需要传入一个结构体, 结构体内重载运算符 bool operator() , 即 ()
括号运算符.
有两个参数 a,b, 返回 true 时把 a 放在前面. 需要注意传入的参数必须是 const 修饰的引用&
类型
每次我们把中间元素赋值后, 顺便把左右两边的子段加入进优先队列.
示意图:
分析得: 时间复杂度为 O ( n ) O(n) O(n)
有以下代码:
#include <iostream>
#include <set>
#include <cstring>
using namespace std;
struct lenCmp{ // 重载括号运算符
bool operator() (const pair<int, int>& a, const pair<int, int>& b) {
int lenOfA = a.second - a.first; // 长度 = 右边 - 左边
int lenOfB = b.second - b.first;
if(lenOfB != lenOfA) return lenOfA > lenOfB; // 当长度不相等时就按长度排列
else return a.first < b.first; // 长度相等时按谁在左边排列
}
};
int main() {
int t; cin >> t;
for (int _ = 0; _ < t; ++_) {
set<pair<int, int>, lenCmp> segmentPQ; // 子段优先队列, PQ 是指 priory queue. 这个 segmentPQ 装载了 pair 类型的元素
// pair 是一个二元数据结构, 可以使用 segmentPQ.first 和 segmentPQ.second 来操作 pair 内的两个元素.
// 这里我存储 segmentPQ.first 是子段的左边, segmentPQ.second 是子段的右边
int n; cin >> n;
int a[n+1]; memset(a, 0, sizeof(a)); // 把数组的所有元素赋值为 0
segmentPQ.insert({1, n}); // 第一个子段就是本身数组
int i = 1; // i 初始为 1
while (!segmentPQ.empty()) { // 直到队列中没有子段可以继续执行
auto target = *segmentPQ.begin(); segmentPQ.erase(segmentPQ.begin()); // 取出最前面的元素(即优先级最高的元素), 然后把他从队列删掉
int l = target.first; // 子段的左边
int r = target.second; // 子段的右边
int mid = (l+r)/2; // 子段的中间
a[mid] = i++; // 中间元素赋值为 i, 然后自增
if(mid > l) segmentPQ.insert({l, mid - 1}); // 把中间元素的左边的子段放进优先队列
if(mid < r) segmentPQ.insert({mid + 1, r}); // 把中间元素的右边的子段放进优先队列
}
for (int k = 1; k <= n; ++k) {
if(k != 1) cout << " ";
cout << a[k];
}
cout << endl;
}
return 0;
}