CF Round #703 Div2 解题补题报告
官方题解](https://codeforces.com/blog/entry/87849)
A题 Shifting Stacks (思维,前缀和)
给定 \(n\) 堆石头堆,第 \(i\) 堆上面有 \(h_i\) 块石头。
现在可以进行多次操作,每次可以将第 \(i\) 堆上面拿走一块石头放到第 \(i+1\) 堆上面(别问能不能拿走最后一堆上面的),可以重复在某一堆上面拿,直到拿空。
问能否在多次操作之后使得 \(h_i\) 呈严格单调递增。
\(1\leq n \leq 100,0 \leq h_i \leq 10^9\)
有个比较显然的性质:最小的且满足要求的数列,是 \(\text{0, 1, 2, ..., n-1}\) 。
如果前面堆上的石头较多,那么总是可以将其转移到后者上面。
那么就有一种比较清晰的思路:从前向后扫一遍,依次判断是否满足 \(\sum\limits_{i=1}^{k}h_i \geq \frac{k(k-1)}{2}\)
如果都能满足则输出 \(YES\) ,反之输出 \(NO\) 。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 110;
int n, h[N];
bool solve()
{
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%d", &h[i]);
LL sum = 0;
for (LL i = 1; i <= n; ++i) {
sum += h[i];
if (sum * 2 < i * (i - 1)) return false;
}
return true;
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
puts(solve() ? "YES" : "NO");
return 0;
}
B题 Eastern Exhibition (排序,中位数)
给定 \(n\) 个房子的坐标,第 \(i\) 个房子的坐标为 \((x_i,y_i)\) 。(所有横纵坐标的值均为整数)
现在想要建一个剧院,该剧院到第 \(i\) 个房子的曼哈顿距离为 \(dist(x)\) ,请求出有多少个坐标,使得剧院建在这个坐标上面时,使得 \(\sum\limits_{i=1}^{n}dist(i)\) 最小?
\(1 \leq n \leq 1000,0 \leq x_i,y_i \leq 10^9\)
如果这题是一维的,那么这题就是很明显的 货舱选址 问题,直接排序,然后选中位数或者最中间两点之间的坐标即可,证明略(高中都手玩过这种 \(f(x)=|x-a|+|x-b|+|x-c|+...\) 这种函数吧?就在中间取到最小值)
那么二维呢?实际上,曼哈顿距离中,\(x,y\) 两者是线性无关的,所以在两个维度上面都选一次就好了,根据乘法原理乘一下就行了。实际上,不仅可以求二位,我们还可以求更高维度。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1010;
int n;
LL x[N], y[N];
void solve()
{
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
scanf("%lld%lld", &x[i], &y[i]);
sort(x + 1, x + n + 1);
sort(y + 1, y + n + 1);
LL ans = (n % 2 == 1) ? 1 :
(x[n/2 + 1] - x[n/2] + 1) * (y[n/2 + 1] - y[n/2] + 1);
printf("%lld\n", ans);
}
int main()
{
int T;
scanf("%d", &T);
while (T--) solve();
return 0;
}
C1题 Guessing the Greatest (easy version) (交互,二分)
这是一道交互题。
已知一个长度为 \(n\) 的数列 \(\{a_n\}\) ,我们在一开始只知道数列的长度。
我们每次可以向系统询问一段区间 \([l,r](l<r)\) 内第二大元素的下表,现在我们需要经过多次询问,找出该数列中最大元素的下标。
询问次数不得超过 \(40\) 次。
\(2\leq n \leq10^5\)
第二次在 \(CodeForces\) 上面见到交互题了,而且还是二分(逃
和上次那个惨不忍睹的题目比起来,这题好写多了(反之我这么想的),虽然也踩了一堆坑
这个数据规模,这个查询限制,很显然是二分了,现在看看咋二分吧
对于区间 \([l,r]\) ,我们分别令 \(i=query(l,r),j=query(l,mid),k=query(mid+1,r)\) 。
那么很显然,如果 \(i=j\),那么说明最大值就在区间 \([l,mid]\) 内,如果 \(i=k\),那么最大值就在区间 \([mid+1,r]\) 内。
如果 \(i\not=j,i\not=k\) 呢?
我第一次就被这卡了,导致我去想了其他思路(逃
实际上呢,上面的思路已经比较接近正确了,我们找几组不太符合的情况,自己手玩一下,就可以修改成正确思路了:
令 \(i=query(l,r)\) ,
- 如果 \(l \leq i \leq mid\) 的话,令 \(j=query(l,mid)\),如果 \(i=j\) ,那么最大值就在 \([l,mid]\) 中,反之则在 \([mid+1,r]\) 中
- 如果 \(mid+1 \leq i \leq r\) 的话,令 \(j=query(mid+1,r)\),如果 \(i=j\) ,那么最大值就在 \([mid+1,r]\) 中,反之则在 \([l,mid]\) 中
遵循这个思路,我们就可以 A 掉这个 \(\text{Easy Version}\) 了(逃
整个询问的次数应该在 \(2*\log_2^{10^5}=2*17=34\) 次这样
#include<bits/stdc++.h>
using namespace std;
int query(int l, int r) {
printf("? %d %d\n", l, r);
fflush(stdout);
int index;
scanf("%d", &index);
return index;
}
void output(int x) {
printf("! %d\n", x);
fflush(stdout);
}
void solve_2(int l, int r) {
int x = query(l, r);
output(x == l ? r : l);
}
void solve_3(int l, int r) {
int x = query(l, r);
if (x == l) solve_2(l + 1, r);
else if (x == r) solve_2(l, r - 1);
else {
if (query(l, l + 1) == x) solve_2(l, l + 1);
else solve_2(r - 1, r);
}
}
int main()
{
int n;
scanf("%d", &n);
int l = 1, r = n;
while (l < r) {
if (r - l == 1 || r - l == 2) {
if (r - l == 1) solve_2(l, r);
if (r - l == 2) solve_3(l, r);
}
int mid = (l + r) >> 1;
int i = query(l, r), j;
if (l <= i && i <= mid) {
j = query(l, mid);
if (i == j) r = mid;
else l = mid + 1;
}
else {
j = query(mid + 1, r);
if (i == j) l = mid + 1;
else r = mid;
}
}
printf("! %d\n", l);
fflush(stdout);
return 0;
}
C2题 Guessing the Greatest (hard version)(交互,二分)
和上一题区别在于,这题的询问次数被卡到了 \(20\) 次。
上一题的代码到这会 \(WA\),我尝试着保存了一些可以维护的状态,但是还是没法控制住询问的次数。那么理论上每次二分只能询问一次了。
搬运一下官方题解的思路:
显然我们先通过上面的思路,先初步确定一下答案区间,例如说答案区间在 \([x,r]\) 上面
我们假设最大值的下标为 \(y\),那么当 \(x < i < y\) 时,\(query(x,i)=?\) (不管是多少,反之不是 \(x\)),而 \(y \leq i \leq r\) 时,\(query(x,i)=x\),这个证明是很显然的,不再赘述
突然发现,如果我们二分 \(y\) ,好像效率会更高些?
总体询问次数为 \(2+\log_2^{10^5}=19\) ,可以过!
代码不给了,感兴趣可以看官方题解(逃
D题 Max Median (二分)
给定一个长度为 \(n\) 的数列 \(\{a_n\}\) ,尝试找出一组长度不短于 \(k\) 的连续子数列,使得其中位数最大。
中位数的定义(和标准可能有所出入):长度为 \(N\) 的数列,其升序排列后的第 \(\lfloor \frac{N+1}{2} \rfloor\) 个元素,为该数列的中位数。
\(1\leq k \leq n \leq 2*10^5,1 \leq a_i\leq n\)
官方题解写的太好了,我直接搬运(翻译)一下得了,就不造轮子了:
-
如果 \(\{a_n\}\) 中只有 \(0\) 和 \(1\) ,我们咋选?
显然,应该选择一个长度不小于 \(k\) 的区间段,使得其中 \(0\) 的数量少于 \(1\) ,这样才能使得中位数是 \(1\) 而非 \(0\) 。
我们也可以将 \(0\) 改为 \(-1\) ,问题就变成了如何使区间和为正数了。
直接求区间和的最大值,那么我们直接枚举 \(r\) ,然后找 \(sum_l\) 最小值,然后相减一下,看看能不能使得区间值大于 \(0\) 的 。朴素复杂度 \(O(n^2)\) ,数据结构优化 \(O(n\log n)\) ,线性递推复杂度 \(O(n)\) 。
-
问题是,现在这数列里面的数也不只 \(0\) 和 \(1\) 啊?
我们可以抽象一下,\(0\) 和 \(1\) 是典型的布尔值,那么对于数列里面的每个数,我们可以对其进行一个布尔表达式的运算,使得结果为 \(0\) 或者 \(1\) 即可(也就是将数分成两类)。
-
所以表达式是啥?
题目要求中位数最大,那我们的表达式应该也和大小有关系。不妨设定一个数 \(x\) ,如果 \(a_i\geq x\) 就将其标记为 \(1\),反之标记为 \(0\) 。显然 \(\min\limits_{1 \leq i \leq n}a_i\leq x \leq \max\limits_{1 \leq i \leq n}a_i\)
-
所以说?
对于 \(x\) 构造出来的新数列,如果我们可以找出一个中位数为 \(1\) 的子数列,说明这个子数列在原数列中的对应数列,他的中位数是不小于 \(x\) 的。
对于这个 \(x\),我们可以枚举,但是,我们似乎还可以使用一些效率更高的方法,例如 \(......\) 二分?
对中位数 \(x\) 在值域上进行二分,每次线性判定,总复杂度 \(O(n \log{n})\)
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, k, a[N], sum[N];
inline int b(int i, int x) { return (a[i] >= x) ? 1 : -1; }
bool solve(int x) {
for (int i = 1; i <= n; ++i)
sum[i] = sum[i - 1] + b(i, x);
int Min = N, Max = - N;
for (int i = k; i <= n; ++i) {
Min = min(Min, sum[i - k]);
Max = max(Max, sum[i] - Min);
}
return (Max > 0);
}
int main()
{
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
int l = 0, r = n;
while (l < r) {
int mid = (l + r + 1) >> 1;
if (solve(mid)) l = mid;
else r = mid - 1;
}
printf("%d", l);
return 0;
}