Codeforces Round 983 (Div.2) 题解

A

题意

\(n\)盏灯,没盏灯都有两个开关,开关可以控制灯的亮灭,如果两个开关都打开或都关上,灯就不会亮。现在不知道哪个开关控制哪盏灯,求最大和最小可能灯亮的个数。

题解

题意有点难懂,但是看样例猜出来开关都开和都关灯都会熄灭的话就好做了。

最少的情况肯定是所有1按照1 1的方式配对,所以1的个数为奇数的话会多出来一个,一定会让一盏灯亮,最小值就是1;为偶数的话最小值就是0。

最大值也是就是1的个数和0的个数的最小值,因为要优先把0和1配对。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <bits/stdc++.h>
using namespace std;
#define int long long

void solve() {
int n;
cin >> n;
vector<int> a(2 * n);
int cnt0 = 0, cnt1 = 0;
for (int i = 0; i < 2 * n; i++) {
cin >> a[i];
}
cnt1 = accumulate(a.begin(), a.end(), 0);
cnt0 = 2 * n - cnt1;
int mn = 0;
int mx = min(cnt0, cnt1);
if (cnt1 & 1) {
mn = 1;
} else {
mn = 0;
}
cout << mn << " " << mx << "\n";
}

signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
cin >> t;
while (t--) {
solve();
}
return 0;
}

B

题意

给定一个奇数\(n\),和一个数\(k\),将按照顺序排列好的\(1,2,...,n\)划分为\(m\)个部分(\(m\)也是奇数),要求每个部分的大小都是奇数,然后从每个部分取出中位数组合在一起,要求这个新组合的中位数也是\(k\),输出每个部分的第一个数或-1代表不可能。

题解

构造题,首先对于无解的情况很可能是我们的突破口。既然都是奇数,那么中位数一定是中间的数,所以\(k\)为1或\(n\)的话,一定不能作为中位数。否则我们就可以划分为三个部分,让中间的部分的中位数是\(k\)。如果\(k\)是偶数,那么直接让\(k\)自己作为一个部分,可以划分为\(1,...,k-1;k;k+1,...,n\),这样三个部分都是奇数大小,满足条件。如果\(k\)是奇数,那就把中间的部分大小设为3,划分为\(1,...,k-2;k-1,k,k+1;k+2,...,n\),这样三个部分也还是奇数大小,满足条件。

此外,需要特判\(n = 1\)的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <bits/stdc++.h>
using namespace std;
#define int long long

void solve() {
int n, k;
cin >> n >> k;
if (n == 1) {
cout << 1 << "\n" << 1 << "\n";
return;
}
if (k == n || k == 1) {
cout << -1 << "\n";
return;
}
cout << 3 << "\n";
if (k & 1) {
cout << 1 << " " << k - 1 << " " << k + 2 << "\n";
} else {
cout << 1 << " " << k << " " << k + 1 << "\n";
}
}

signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
cin >> t;
while (t--) {
solve();
}
return 0;
}

C

题意

给定一个序列\(a_i\),对于下标\(i,j\),可以进行若干次\(a_i = a_j\)的操作,要求最后生成的序列里任取三个数都满足可以构成三角形,求最小操作次数。

题解

构成三角形要求两边之和大于第三边,既然要求任取三个数都满足,所以需要最小的两个数之和都大于最大的数,所以自然需要一次排序。一个想法是从左到右和从右到左各遍历一次,维护一个能够满足的范围,但是这样的问题是:还有可能满足条件的范围在中间,所以这样贪心的思路好像并不可行。不过,我们基本的思路已经出来了:就是维护能够满足条件的范围,不妨把每个左端点能拓展到的位置都尝试一遍。

我们用滑动窗口的思想,维护一个满足条件的区间,要求区间内任取两个数都是可以构成三角形的,我们在双指针移动的过程中更新答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <bits/stdc++.h>
using namespace std;
#define int long long

void solve() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
sort(a.begin(), a.end());
int ans = n - 1;
for (int i = 0, j = 0; i < n - 1; i++) {
while (j + 1 < n && a[i] + a[i + 1] > a[j + 1]) {
j++;
}
ans = min(ans, n - (j - i + 1));
}
cout << ans << "\n";
}

signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
cin >> t;
while (t--) {
solve();
}
return 0;
}

D

题意

交互题

给定一颗\(n\)个节点的数,编号为\(0,1,...,n-1\),设每个节点的父亲节点为\(p_i\),这棵树满足如下性质:

  1. 如果去掉0节点,剩余的部分由若干条链组成
  2. 对于节点\(x,y\),如果\(x < y\),那么必有\(p_x\le p_y\)
  3. 节点1有两个相邻的节点(包括节点0)

交互:你可以询问两个节点的路径上是否有0节点,不超过\(2n-6\)次。

输出\(p_1,p_2,...,p_{n-1}\)

题解

我们首先根据树的性质,思考这棵树的大致形状。

根据性质1,我们知道除了根节点,这棵树的每一个节点只有一个儿子节点。

根据性质2,我们容易想到:这棵树节点的排列顺序一定是分层排列,每一层节点从小到大,父亲节点也是从小到大的。否则,会违反性质2。并且,一旦一层中某个节点在下一层没有子节点,那么之后这个节点再也不可能有子节点了。

大概确定树的形状后,我们可以通过类似BFS的方式求出每个节点的父节点。具体为:询问当前节点和上一层的节点,如果回答是1,说明不是父亲,否则说明找到父亲了。

接下来思考性质3的作用:其实就是帮助我们确定有多少条链,我们从2开始的节点\(i\)询问1和\(i\),直到回答为0,此时的\(i\)就是1的儿子节点,之前的\(i\)都是和1同层的节点。

然后,我们依次询问每一个节点的父亲,如果不是,则说明询问的父亲节点不再可能是任何节点的父亲了,由于这棵树就是每一层按顺序排列的,我们依次询问即可。

关于\(2n-6\)的询问次数的正确性:我们注意到一个性质:从上到下的每一层的节点数量是非递增的。对于一个节点,最坏的情况就是它的父亲是它当前层的第一个节点,这样他就会遍历一整层之后才能确定父亲。不过,一旦发生这种情况后,后面的遍历中链的数量就会减少,除掉这种情况,平均还是1次询问就能确定一个节点。如果一层节点的数量很多,说明在找节点1的儿子时已经确定了不少节点,后续要么很快找到父亲,要么很快退化成链;如果一层节点的数量较少,那么就是链上询问,额外的次数消耗不大。在\(2n-6\)的询问次数下还是可以完成任务的。(具体证明我也不太会)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <bits/stdc++.h>
using namespace std;
#define int long long

void solve() {
int n;
cin >> n;
auto query = [&](int x, int y) {
cout << "? " << x << " " << y << "\n";
cout.flush();
int exist;
cin >> exist;
return exist;
};
vector<int> p(n);
p[0] = p[1] = 0;
int a = 2;
while (query(a, 1)) {
p[a++] = 0;
}
p[a++] = 1;
int l = 2;
for (int i = a; i < n; i++) {
while (query(i, l)) {
l++;
}
p[i] = l++;
}
cout << "! ";
for (int i = 1; i < n; i++) {
cout << p[i] << " \n"[i == n - 1];
}
cout.flush();
}

signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
cin >> t;
while (t--) {
solve();
}
return 0;
}