CF 1093 Div
CF 1093 (Div.2) A~E1题解
A
这个题有两种做法。
做法1(暴力)
考虑新建两个大根堆,一个存储已经大于 \(c\) 的数,另一个存储小于等于 \(c\) 的数。
由于删除第二个堆中的数已经对答案没有任何帮助,反正这些最后都要删掉,所以只需要删掉小于等于 \(c\) 的数中的最大值就行了,剩下的拿出来乘 \(2\),然后放进去。
时间复杂度 \(O(Tn^2logn)\),显然能过。
做法2(转化)
注意到每次暴力操作会很慢,不如转化一下,转化成每次把 \(c\) 变成 \(\lceil \frac{c}{2} \rceil\),然后每次删除 \(\leq c\) 的最大值。
瓶颈在排序,复杂度 \(O(Tnlogn)\) 显然更快。
或许有人会疑问,为什么是上取整啊?
请看这个例子:
| 上取整 \(c\) | 3 | 2 | 1 |
|---|---|---|---|
| 下取整 \(c\) | 3 | 1 | 0 |
| 实际情况 | 1 | 2 | 4 |
如果是下取整的话,它就会被判断为花费 \(1\),但是上取整的判断是正确的\
代码就不给出了,非常简单。
B
这个题要求我们从一个数列的左右删除一个数,使得删除的数依次排成一个没有 \(5\) 个及以上连续上升/下降的序列。
感性理解。
尝试构造一个高低不平的序列,像这样:

注意现在只是理想状态,不一定能达到。
直接看代码应该能看懂:
#include <iostream>
#include <algorithm>
using namespace std;
const int MAXN = 200010;
int a[MAXN];
char ans[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int l = 1, r = n;
int last = 0;
int op = 1;
int cnt = 0;
while (l <= r) {
if (op == 1) {
if (max(a[l], a[r]) > last) {
if (a[l] > a[r]) {
ans[cnt++] = 'L';
last = a[l];
l++;
} else {
ans[cnt++] = 'R';
last = a[r];
r--;
}
op = 0;
} else {
if (a[l] < a[r]) {
ans[cnt++] = 'L';
last = a[l];
l++;
} else {
ans[cnt++] = 'R';
last = a[r];
r--;
}
}
} else {
if (min(a[l], a[r]) < last) {
if (a[l] < a[r]) {
ans[cnt++] = 'L';
last = a[l];
l++;
} else {
ans[cnt++] = 'R';
last = a[r];
r--;
}
op = 1;
} else {
if (a[l] > a[r]) {
ans[cnt++] = 'L';
last = a[l];
l++;
} else {
ans[cnt++] = 'R';
last = a[r];
r--;
}
}
}
}
ans[cnt] = '\0';
cout << ans << '\n';
}
return 0;
}
C
我们构造几个数据进行观察。
首先对于n=1的数组,显然可以构造出来。
再看n=2的数组。
- 如果两个元素相等,可以2步构造出来。比如{3, 3},第一次x=3,得到{3, 0},第二次x=3,得到
- 如果a[1] > a[2],显然也可以构造出来。比如{3, 2},第一次x=3,得到{3, 0},第二次x=2,得到
现在来看a[1] < a[2]的情况,什么时候可以构造出来。
我们可以逐个枚举
{1, 2} // no
{1, 3} // no
{2, 3} // yes: {2, 0} -> {2, 1} -> {2, 3}
{2, 4} // no
{3, 4} // yes: {3, 0} -> {3, 1} -> {3, 4}
{3, 5} // yes: {3, 0} -> {3, 2} -> {3, 5}
{3, 6} // no
根据上面,我们猜测a[2] < 2*a[1]时,是可构造的。我们研究出一个构造套路
{y, y+y-1} // yes: {y, 0} -> {y, y-1} -> {y, y+y-1}
而当a[2] >= 2*a[1]时,此时显然不可解。
推广到任意长度,则有 mn < a[i] < mn*2-1,其中mn为前缀最小值。
const int N = 200010;
int n;
int a[N];
void solve() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
bool f = 1;
int mn = a[1];
for (int i = 2; i <= n; ++i) {
if (a[i] >= mn) {
if (a[i] >= mn + mn) {
f = 0;
break;
}
} else {
mn = a[i];
}
}
printf("%s\n", f ? "YES" : "NO");
}
D
暴力肯定不行,很显然是 \(O(n^3)=O(TLE)\) 的
考虑这个数列是一个下降的数列。
那么它的每一个子序列都是下降的。
答案就是考虑每一个元素对于答案的贡献(出现在多少个子序列中)
而题目中给出的条件:
那么就不可能出现三个连续上升的元素。
所以答案就是假设所有的元素下降的答案扣掉含有上升一次(形如 \(a_i \lt a_{i+1}\))的段的个数,因为长度会 \(-1\)。
总贡献为 \([1,i] * [i,n]\)。
扣掉的形如 \(a_i \lt a_{i+1}\) 的贡献为 \([1,i] * [i+1,n]\)。
代码在下方。
const int maxn = 500010;
#define ll long long
int n;
int a[maxn];
void solve() {
scanf("%d", &n);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
ll ans = 0;
for (int i = n; i >= 1; --i) { // 总贡献
ans += 1LL * i * (n - i + 1);
}
for (int i = 1; i < n; ++i) {
if (a[i] < a[i+1]) { // 减去包含[i,i+1]的,不符合预期的所有区间
ans -= 1LL * i * (n - i);
}
}
printf("%lld\n", ans);
}
E
看到中位数,想起来 二分 和 排序。
对于每个 \(\texttt{submedian}\),考虑二分 \(v_{max}\)。
把所有小于 \(v_{max}\) 的数标记为 \(-1\),剩下的标记为 \(1\)。
考虑取长度 \(\geq k\) 的子段和。如果子段和大于等于 \(0\),那么这个可取。
有人会问,为什么不是等于 \(0\)。
因为我们的二分是在不断缩小范围,最后一定会到最优解。
刚刚的子段和问题,是个标准的双指针。
时间复杂度 \(O(nlogn)\)。
const int maxn = 300010;
#define ll long long
int n, k;
int a[maxn];
int pre[maxn];
bool check(int val, int &ansl, int &ansr) {
pre[0] = 0;
for (int i = 1; i <= n; ++i) {
pre[i] = pre[i-1] + (a[i] >= val ? 1 : -1);
}
// j表示历史最小pre对应的下标 l-1
for (int j = 0, i = k; i <= n; ++i) {
if (pre[j] > pre[i-k]) {
j = i - k;
}
if (pre[i] >= pre[j]) {
ansl = j + 1;
ansr = i;
return true;
}
}
return false;
}
void solve() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i) {
scanf("%d", &a[i]);
}
int l = 1, r = n;
int mid;
int res = 1;
int pos1, pos2;
while (l <= r) {
mid = (l + r) / 2;
if (check(mid, pos1, pos2)) {
res = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
printf("%d %d %d\n", res, pos1, pos2);
}

浙公网安备 33010602011771号