B. Eastern Exhibition
中位数。
题意:
在二维坐标系中,给定n个坐标点。让我们找出有多少个点能够满足到这n个点的距离总和最小。
题解:
这道题主要考察中位数。分析发现当n为奇数时,到n个点的距离总和最小的点只有一个。
当n为偶数时,分别对n个点的x和y坐标进行排序,然后根据中间两个数的值求出一个区间,所有在区间内部的点都满足题目要求。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll x[1010], y[1010];
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> x[i] >> y[i];
}
sort(x + 1, x + 1 + n);
sort(y + 1, y + 1 + n);
ll ans = 1;
if (n % 2 == 0) {
ll midx = x[n / 2 + 1] - x[n / 2] + 1; // 求出答案的区间长度
ll midy = y[n / 2 + 1] - y[n / 2] + 1;
cout << midx * midy << endl;
continue;
}
else {
cout << 1 << endl;
continue;
}
}
return 0;
}
C. Guessing the Greatest
这是一道交互题。二分。
题意:
给我们一个长度为n的一个排列,每次我们可以对一个区间进行询问,每次询问返回当前区间第二大的元素下标。让我们在有限次数内,找到这个排列的最大值的下标。
题解:
基本上交互题都是使用二分解决。这道题也不例外。我的做法就是,在第一次询问时,直接询问
1
n
1~n
1 n这整个区间,找到第二大的元素下标,然后判断最大的元素在该元素的左侧还是右侧。判断的方法就是,比如目前第二大元素的下标是p,那么我们可以询问
1
p
1~p
1 p,如果返回的结果还是p,那么说明最大元素在该区间内,反之,则在右侧区间。
找到在哪半个区间之后,可以对该区间进行二分。并根据每次询问结果是否等于区间第二大元素下标来判断最大元素在哪半边即可。
具体细节参考代码
#include <bits/stdc++.h>
using namespace std;
int check(int l, int r) {
cout << "? " << l << " " << r << endl;
int x;
cin >> x;
return x;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
int l = 1, r = n;
if (l == r) {
cout << "! " << l << endl;
cout.flush();
return 0;
}
cout << "? " << 1 << " " << n << endl;
cout.flush();
int now;
cin >> now;
if (now == 1) {
l = now + 1;
r = n;
while (l < r) {
int mid = l + r >> 1;
if (check(now, mid) == now) {
r = mid;
}
else {
l = mid + 1;
}
}
cout << "! " << l << endl;
cout.flush();
}
else if (now == n) {
r = now - 1;
l = 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (check(mid, now) == now) {
l = mid;
}
else {
r = mid - 1;
}
}
cout << "! " << l << endl;
cout.flush();
}
else if (check(now, r) == now) {
l = now + 1;
while (l < r) {
int mid = l + r >> 1;
if (check(now, mid) == now) {
r = mid;
}
else {
l = mid + 1;
}
}
cout << "! " << l << endl;
cout.flush();
}
else {
r = now - 1;
while (l < r) {
int mid = l + r + 1 >> 1;
if (check(mid, now) == now) {
l = mid;
}
else {
r = mid - 1;
}
}
cout << "! " << l << endl;
cout.flush();
}
return 0;
}
D. Max Median
二分 + 前缀和。
题意:
题目给了长度为n的数组a,对于该数组的每个子区间都有一个中位数,此中位数的就是该区间中处于第
⌊
n
−
1
2
⌋
\lfloor \frac{n-1}2\rfloor
⌊2n−1⌋位置的元素值。题目让我们求出所有长度至少为k的区间当中的最大中位数。
题解:
这道题目需要进行一个简单的转化。我们要考虑到这道题目中位数的定义,假设当前的中位数是x,那么只要区间当中大于等于x的元素数量比小于x的元素数量多即可保证该区间的中位数一定大于等于x。根据这个性质,我们可以二分这个最大的中位数,根据当前的二分值来判断,当前数组中是否有长度至少为k的区间包含有大于等于该值的中位数即可。
不过怎么判断呢?
这里我们需要进行一个简单的转化。假设现在二分的中位数值为x,那么我们将数组a中大于等于x的元素值设为1,小于x的元素值设为-1。那么我们就可以使用前缀和的思想来进行判断。我们求出所有转化之后的数组前缀和pre,同时计算出每个位置的之前的前缀和最小值minpre。那么,转化完成之后,我们从第k个位置进行枚举,因为要求区间长度至少为k。假设当前枚举的位置是i,那么判断 p r e [ i ] − m i n p r e [ i − k ] pre[i]-minpre[i-k] pre[i]−minpre[i−k]是否大于0。如果大于0,表明当前区间中大于等于二分值的元素个数一定比小于二分值的元素个数多,也就是当前区间的中位数一定大于等于二分值,这时候就可以更新二分边界,进行下一步二分,知道找出最大的中位数。
这道题目和最佳牛围栏这道题几乎一样。
具体实现可以看代码:
#include <bits/stdc++.h>
using namespace std;
int a[MAXN], pre[MAXN], minpre[MAXN], n, k;
bool check(int x) {
for (int i = 1; i <= n; i++) {
pre[i] = pre[i - 1] + (a[i] >= x ? 1 : -1);
minpre[i] = min(minpre[i - 1], pre[i]);
}
for (int i = k; i <= n; i++) {
if (pre[i] - minpre[i - k] > 0) { // 表明该区间一定满足中位数大于等于当前二分值
return true;
}
}
return false;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int l = 1, r = n; // 二分中位数的值
while (l < r) {
int mid = l + r + 1 >> 1;
if (check(mid)) {
l = mid;
}
else {
r = mid - 1;
}
}
cout << l << endl;
return 0;
}