Codeforces Round 983 div2 个人题解(A~D)
Dashboard - Codeforces Round 983 (Div. 2) - Codeforces
火车头
#define _CRT_SECURE_NO_WARNINGS 1
#include <algorithm>
#include <array>
#include <bitset>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <chrono>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <list>
#include <map>
#include <numeric>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
using namespace std;
#define ft first
#define sd second
#define yes cout << "yes\n"
#define no cout << "no\n"
#define Yes cout << "Yes\n"
#define No cout << "No\n"
#define YES cout << "YES\n"
#define NO cout << "NO\n"
#define pb push_back
#define eb emplace_back
#define all(x) x.begin(), x.end()
#define all1(x) x.begin() + 1, x.end()
#define unq_all(x) x.erase(unique(all(x)), x.end())
#define unq_all1(x) x.erase(unique(all1(x)), x.end())
#define sort_all(x) sort(all(x))
#define sort1_all(x) sort(all1(x))
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3fLL
#define RED cout << "\033[91m" // 红色
#define GREEN cout << "\033[92m" // 绿色
#define YELLOW cout << "\033[93m" // 蓝色
#define BLUE cout << "\033[94m" // 品红
#define MAGENTA cout << "\033[95m" // 青色
#define CYAN cout << "\033[96m" // 青色
#define RESET cout << "\033[0m" // 重置
template <typename T>
void Debug(T x, int color = 1)
{
switch (color)
{
case 1:
RED;
break;
case 2:
YELLOW;
break;
case 3:
BLUE;
break;
case 4:
MAGENTA;
break;
case 5:
CYAN;
break;
default:
break;
}
cout << x;
RESET;
}
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
// typedef __int128_t i128;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
typedef pair<ld, ld> pdd;
typedef pair<ll, int> pli;
typedef pair<string, string> pss;
typedef pair<string, int> psi;
typedef pair<string, ll> psl;
typedef tuple<int, int, int> ti3;
typedef tuple<ll, ll, ll> tl3;
typedef tuple<ld, ld, ld> tld3;
typedef vector<bool> vb;
typedef vector<int> vi;
typedef vector<ll> vl;
typedef vector<string> vs;
typedef vector<pii> vpii;
typedef vector<pll> vpll;
typedef vector<pli> vpli;
typedef vector<pss> vpss;
typedef vector<ti3> vti3;
typedef vector<tl3> vtl3;
typedef vector<tld3> vtld3;
typedef vector<vi> vvi;
typedef vector<vl> vvl;
typedef queue<int> qi;
typedef queue<ll> ql;
typedef queue<pii> qpii;
typedef queue<pll> qpll;
typedef queue<psi> qpsi;
typedef queue<psl> qpsl;
typedef priority_queue<int> pqi;
typedef priority_queue<ll> pql;
typedef map<int, int> mii;
typedef map<int, bool> mib;
typedef map<ll, ll> mll;
typedef map<ll, bool> mlb;
typedef map<char, int> mci;
typedef map<char, ll> mcl;
typedef map<char, bool> mcb;
typedef map<string, int> msi;
typedef map<string, ll> msl;
typedef map<int, bool> mib;
typedef unordered_map<int, int> umii;
typedef unordered_map<ll, ll> uml;
typedef unordered_map<char, int> umci;
typedef unordered_map<char, ll> umcl;
typedef unordered_map<string, int> umsi;
typedef unordered_map<string, ll> umsl;
std::mt19937_64 rng(std::chrono::steady_clock::now().time_since_epoch().count());
template <typename T>
inline T read()
{
T x = 0;
int y = 1;
char ch = getchar();
while (ch > '9' || ch < '0')
{
if (ch == '-')
y = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9')
{
x = (x << 3) + (x << 1) + (ch ^ 48);
ch = getchar();
}
return x * y;
}
template <typename T>
inline void write(T x)
{
if (x < 0)
{
putchar('-');
x = -x;
}
if (x >= 10)
{
write(x / 10);
}
putchar(x % 10 + '0');
}
/*#####################################BEGIN#####################################*/
void solve()
{
}
int main()
{
ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0);
// freopen("test.in", "r", stdin);
// freopen("test.out", "w", stdout);
int _ = 1;
std::cin >> _;
while (_--)
{
solve();
}
return 0;
}
/*######################################END######################################*/
// 链接:
A. Circuit
Alice 刚刚制作了一个带有 n n n 个灯和 2 n 2n 2n 个开关的电路。每个组件(灯或开关)都有两种状态:开或关。灯和开关的排列方式如下:
每个灯连接到恰好两个个开关。
每个开关连接到恰好一个灯。不知道每个开关连接到哪个灯。
当所有开关都关闭时,所有灯也会关闭。
如果切换开关(从开到关,或反之亦然),则连接到它的灯的状态也会切换。
Alice 把只显示 2 n 2n 2n 个开关状态的电路带给她的妹妹 Iris,并给了她一个谜语:可以打开的灯的最小和最大数量是多少?
Iris 非常了解她妹妹的滑稽动作,她只用了一秒钟就给了 Alice 一个正确的答案。你能做到同样的事情吗?
输入
每个测试由多个测试用例组成。第一行包含一个整数
t
t
t (
1
≤
t
≤
500
1 \leq t \leq 500
1≤t≤500) — 测试用例的数量。测试用例的描述如下。
每个测试用例的第一行包含一个整数 n n n ( 1 ≤ n ≤ 50 1 \leq n \leq 50 1≤n≤50) — 电路中的灯的数量。
每个测试用例的第二行包含 2 n 2n 2n 个整数 a 1 , a 2 , … , a 2 n a_1,a_2,…,a_{2n} a1,a2,…,a2n ( 0 ≤ a i ≤ 1 0 \leq a_i \leq 1 0≤ai≤1) — 电路中开关的状态。 a i = 0 a_i=0 ai=0 表示第 i i i 个开关关闭, a i = 1 a_i=1 ai=1 表示第 i i i 个开关打开。
输出
对于每个测试用例,输出两个整数——分别表示可以打开的灯的最小数量和最大数量。
示例
输入
5
1
0 0
1
0 1
1
1 1
3
0 0 1 0 1 0
3
0 1 1 1 0 0
输出
0 0
1 1
0 0
0 2
1 3
提示
在第一个测试用例中,电路中只有一个灯,且没有开关开启,因此灯肯定是关闭的。
在第二个测试用例中,电路中只有一个灯,但与之连接的一个开关是开启的,因此灯是开启的。
在第三个测试用例中,电路中只有一个灯,且两个开关都是开启的,因此灯是关闭的,因为它被切换了两次。
在第四个测试用例中,为了没有灯开启,开关可以这样排列:
开关
1
1
1 和开关
4
4
4 连接到灯
1
1
1。由于两个开关都关闭,灯
1
1
1 也关闭。
开关
2
2
2 和开关
6
6
6 连接到灯
2
2
2。由于两个开关都关闭,灯
2
2
2 也关闭。
开关
3
3
3 和开关
5
5
5 连接到灯
3
3
3。两个开关都是开启的,因此灯
3
3
3 被切换了两次,从初始关闭状态保持关闭。
而为了开启
2
2
2 个灯,开关可以这样排列:
开关
1
1
1 和开关
2
2
2 连接到灯
1
1
1。由于两个开关都关闭,灯
1
1
1 也关闭。
开关
3
3
3 和开关
4
4
4 连接到灯
2
2
2。由于开关
3
3
3 是开启的而开关
4
4
4 是关闭的,灯
2
2
2 从初始关闭状态被切换了一次,因此它是开启的。
开关
5
5
5 和开关
6
6
6 连接到灯
3
3
3。由于开关
5
5
5 是开启的而开关
6
6
6 是关闭的,灯
3
3
3 从初始关闭状态被切换了一次,因此它是开启的。
解题思路
读题可知一个01
对应一个开着的灯,而00
和11
对应着关着的灯。
所以如果1
为偶数,则最小开灯数为
0
0
0,1
和0
内部之间互相匹配,否则为
1
1
1。
最大开灯数则为 min ( cnt0 , cnt1 ) \min(\text{cnt0},\text{cnt1}) min(cnt0,cnt1)
代码实现
void solve()
{
int n;
cin >> n;
vi a(n * 2);
int cnt0 = 0;
int cnt1 = 0;
for (int i = 0; i < n * 2; i++)
{
cin >> a[i];
if (a[i] == 0)
cnt0++;
else
cnt1++;
}
cout << (cnt1 & 1) << " " << min(cnt0, cnt1) << endl;
}
B. Medians
给定一个数组 $ a = [1, 2, \ldots, n] $,其中 $ n $ 为奇数,以及一个整数 $ k $。
您的任务是选择一个奇数正整数 $ m $,并将 $ a $ 拆分为 $ m $ 个子数组 $ b_1, b_2, \ldots, b_m $,使得:
- 数组 $ a $ 的每个元素都只属于一个子数组。
- 对于所有 $ 1 \leq i \leq m $, $ |b_i| $ 为奇数,即每个子数组的长度为奇数。
- $ \text{median}([\text{median}(b_1), \text{median}(b_2), \ldots, \text{median}(b_m)]) = k $,即所有子数组中位数的数组中位数必须等于 $ k 。 。 。 \text{median}© $ 表示数组 $ c $ 的中位数。
输入
每个测试由多个测试用例组成。第一行包含一个整数 $ t $ ($ 1 \leq t \leq 5000 $)——测试用例数。测试用例说明如下。
每个测试用例的第一行包含两个整数 $ n $ 和 $ k $ ($ 1 \leq k \leq n < 2 \cdot 10^5 $,且 $ n $ 为奇数)——数组 $ a $ 的长度和所有子数组中位数数组的期望中位数。
保证所有测试用例中 $ n $ 的总和不超过 $ 2 \cdot 10^5 $。
输出
对于每个测试用例:
如果没有合适的分区,则单行输出
−
1
-1
−1。
否则,在第一行输出一个奇数整数 $ m $ ($ 1 \leq m \leq n $),在第二行输出 $ m $ 个不同的整数 $ p_1, p_2, p_3, \ldots, p_m $ ($ 1 = p_1 < p_2 < p_3 < \ldots < p_m \leq n $)——表示每个子数组的左边界。具体来说,对于有效答案 $ [p_1, p_2, \ldots, p_m] $:
- $ b_1 = [a_{p_1}, a_{p_1 + 1}, \ldots, a_{p_2 - 1}] $
- $ b_2 = [a_{p_2}, a_{p_2 + 1}, \ldots, a_{p_3 - 1}] $
- … \ldots …
- $ b_m = [a_{p_m}, a_{p_m + 1}, \ldots, a_n] $
如果有多个解决方案,您可以输出其中任何一个。
示例
输入
4
1 1
3 2
3 3
15 8
输出
1
1
3
1 2 3
-1
5
1 4 7 10 13
提示
在第一个测试用例中,给定的分区有 $ m = 1 $ 且 $ b_1 = [1] $。显然 $ \text{median}([\text{median}([1])]) = \text{median}([1]) = 1 $。
在第二个测试用例中,给定的分区有 $ m = 3 $ 且:
- $ b_1 = [1] $
- $ b_2 = [2] $
- $ b_3 = [3] $
因此, $ \text{median}([\text{median}([1]), \text{median}([2]), \text{median}([3])]) = \text{median}([1, 2, 3]) = 2 $。
在第三个测试用例中,对于 $ k = 3 $ 并没有有效的分区。
在第四个测试用例中,给定的分区有 $ m = 5 $ 且:
- $ b_1 = [1, 2, 3] $
- $ b_2 = [4, 5, 6] $
- $ b_3 = [7, 8, 9] $
- $ b_4 = [10, 11, 12] $
- $ b_5 = [13, 14, 15] $
因此, $ \text{median}([\text{median}([1, 2, 3]), \text{median}([4, 5, 6]), \text{median}([7, 8, 9]), \text{median}([10, 11, 12]), \text{median}([13, 14, 15])]) = \text{median}([2, 5, 8, 11, 14]) = 8 $。
解题思路
由于分区长度任意,我们考虑把 k k k单独拎出来放在最中间的分区,然后再左右两侧划分相同数量的分区。
k k k为奇数,我们可以 [ 1 , 1 ] [ 2 , k − 1 ] [ k , k ] [ k + 1 , k + 1 ] [ k + 2 , n ] [1,1][2,k-1][k,k][k+1,k+1][k+2,n] [1,1][2,k−1][k,k][k+1,k+1][k+2,n]划分。
k k k为偶数,我们可以 [ 1 , k − 1 ] [ k ] [ k + 1 , n ] [1,k-1][k][k+1,n] [1,k−1][k][k+1,n]划分
还要特判一下 k = 1 , k = n k=1,k=n k=1,k=n和 k k k刚好为中位数的情况
代码实现
void solve()
{
ll n, k;
cin >> n >> k;
if (k == (n + 1) / 2)
{
cout << "1\n1\n";
return;
}
if (k == 1 || k == n)
{
cout << "-1\n";
return;
}
if (k & 1)
{
cout << "5\n";
cout << 1 << " " << 2 << " " << k << " " << k + 1 << " " << k + 2 << "\n";
}
else
{
cout << "3\n";
cout << 1 << " " << k << " " << k + 1 << "\n";
}
}
C. Trinity
您将获得一个包含 $ n $ 个元素 $ a_1,a_2,…,a_n $ 的数组 $ a $。
您可以执行以下操作任意次数(可能是 0):
选择两个整数 $ i $ 和 $ j $,其中 $ 1 \leq i,j \leq n $,并分配 $ a_i := a_j $。找出使数组 $ a $ 满足条件所需的最少操作数:
对于每个成对不同的索引三元组 ( x , y , z ) (x,y,z) (x,y,z) ($ 1 \leq x,y,z \leq n $, $ x \neq y $, $ y \neq z $, $ x \neq z $),存在一个非退化三角形,其边长为 $ a_x 、 、 、 a_y $ 和 $ a_z $,即 $ a_x + a_y > a_z 、 、 、 a_y + a_z > a_x $ 和 $ a_z + a_x > a_y $。
输入
每个测试由多个测试用例组成。第一行包含一个整数 $ t $ ($ 1 \leq t \leq 10^4 $)——测试用例的数量。测试用例的描述如下。
每个测试用例的第一行包含一个整数 $ n $ ($ 3 \leq n \leq 2 \cdot 10^5 $)——数组 $ a $ 中的元素数量。
每个测试用例的第二行包含 $ n $ 个整数 $ a_1,a_2,…,a_n $ ($ 1 \leq a_i \leq 10^9 $)——数组 $ a $ 的元素。
保证所有测试用例的 $ n $ 之和不超过 $ 2 \cdot 10^5 $。
输出
对于每个测试用例,输出一个整数——所需的最少操作数。
示例
输入
4
7
1 2 3 4 5 6 7
3
1 3 2
3
4 5 3
15
9 3 8 1 6 5 3 8 2 1 4 2 9 4 7
输出
3
1
0
8
提示
在第一个测试用例中,可以进行以下操作:
- 赋值 $ a_1 := a_4 = 4 $,数组变为 $ [4,2,3,4,5,6,7] $。
- 赋值 $ a_2 := a_5 = 5 $,数组变为 $ [4,5,3,4,5,6,7] $。
- 赋值 $ a_7 := a_1 = 4 $,数组变为 $ [4,5,3,4,5,6,4] $。
可以证明,最终数组中任意三元组的元素都能形成一个非退化三角形,且不可能用少于 3 次操作得到答案。
在第二个测试用例中,我们可以赋值 $ a_1 := a_2 = 3 $,使数组 $ a = [3,3,2] $。
在第三个测试用例中,由于 3、4 和 5 是有效的三角形边长,因此不需要对数组进行任何操作。
解题思路
观察题目要求和三角形的性质发现,我们实际上是在求一个极差小于最小值的区间,即 min × 2 > max \min\times2>\max min×2>max。
所以我们可以先给数组排个序,枚举最小值,然后二分查找最大值,我们就得到了所需的区间。
但是!!!
这种做法忽略了最小值只有一个的情况,如果最小值只有一个且次小值加最小值大于最大值,也是可行的。
所以,我们应该枚举最小值和次小值,然后二分查找小于他们之和的最大值,所找到的区间即为三角形可行区间。
区间外的元素就是我们需要操作的元素。
代码实现
void solve()
{
int n;
cin >> n;
vector<ll> a(n); // 注意a的范围为1e9,最小值和次值相加时可能会溢出
for (int i = 0; i < n; i++)
{
cin >> a[i];
}
sort(all(a));
int ans = inf;
for (int i = 1; i < n; i++)
{
int p = lower_bound(all(a), a[i] + a[i - 1]) - a.begin();
ans = min(ans, n - p + i - 1);
}
cout << ans << endl;
}
D. Genokraken
这是一个交互式问题。
清理完水边区域后,Gretel 发现了一个名为 Genokraken 的怪物,她将其控制起来用于科学研究。
怪物的神经系统可以构造为一棵由 $ n $ 个节点组成的树,编号从 $ 0 $ 到 $ n−1 $,以节点 $ 0 $ 为根。
Gretel 的目标是了解怪物神经系统的确切结构——更具体地说,她想知道树的值 $ p_1,p_2,…,p_{n−1} $,其中 $ p_i $ ($ 0 \leq p_i < i $)是节点 $ i $ ($ 1 \leq i \leq n−1 $)的直接父节点。
她不知道节点的具体放置方式,但她知道一些方便的事实:
- 如果我们删除根节点 $ 0 $ 和所有相邻边,这棵树将变成仅由路径组成的森林。最初与节点 $ 0 $ 相邻的每个节点将成为某条路径的终点。
- 节点的索引方式为,如果 $ 1 \leq x \leq y \leq n−1 $,则 $ p_x \leq p_y $。
- 节点 $ 1 $ 恰好有两个相邻节点(包括节点 $ 0 $)。
Gretel 可以对包含单元进行查询:
“? a b”($ 1 \leq a,b < n , , , a \neq b $)——单元将检查节点 $ a $ 和 $ b $ 之间的简单路径是否包含节点 $ 0 $。但是,为了避免过度刺激生物而导致意外后果,Gretel 最多希望查询 $ 2n−6 $ 次。
输入
每个测试由多个测试用例组成。第一行包含一个整数 $ t $ ($ 1 \leq t \leq 500 $)——测试用例的数量。测试用例的描述如下。
每个测试用例的第一行包含一个整数 $ n $ ($ 4 \leq n \leq 10^4 $)——Genokraken神经系统中的节点数。
保证所有测试用例的 $ n $ 之和不超过 $ 10^4 $。
交互
对于每个测试用例,交互从读取整数 $ n $ 开始。
然后您可以进行以下类型的查询:
“? a b”(不带引号)($ 1 \leq a,b < n , , , a \neq b $)。查询后,读取一个整数 $ r $——查询的答案。您最多可以使用 $ 2n−6 $ 个此类查询。
- 如果节点 $ a $ 和 $ b $ 之间的简单路径不包含节点 $ 0 $,您将得到 $ r=0 $。
- 如果节点 $ a $ 和 $ b $ 之间的简单路径包含节点 $ 0 $,您将得到 $ r=1 $。
- 如果您进行了超过 $ 2n−6 $ 次查询或进行了无效查询,您将得到 $ r=−1 $。您需要在此之后终止以获得“错误答案”判决。
当您找出结构时,以“! $ p_1 \ p_2 \ \ldots \ p_{n−1} $”(不带引号)格式输出一行,其中 $ p_i $ ($ 0 \leq p_i < i $)表示节点 $ i $ 的直接父节点的索引。此查询不计入 $ 2n−6 $ 查询限制。
解决一个测试用例后,程序应立即转到下一个测试用例。解决所有测试用例后,程序应立即终止。
示例
输入
3
4
1
5
1
0
9
输出
? 2 3
! 0 0 1
? 2 3
? 2 4
! 0 0 1 2
! 0 0 0 1 3 5 6 7
提示
在第一个测试用例中,Genokraken 的神经系统形成了以下树:
对“? 2 3”的答案是 $ 1 $。这意味着节点 $ 2 $ 和 $ 3 $ 之间的简单路径包含节点 $ 0 $。
在第二个测试用例中,Genokraken 的神经系统形成了以下树:
对“? 2 3”的答案是 $ 1 $。这意味着节点 $ 2 $ 和 $ 3 $ 之间的简单路径包含节点 $ 0 $。
对“? 2 4”的答案是 $ 0 $。这意味着节点 $ 2 $ 和 $ 4 $ 之间的简单路径不包含节点 $ 0 $。
解题思路
说人话的题意:
给了你一棵特殊的树,其节点编号为 0 ∼ n − 1 0\sim n-1 0∼n−1,根节点为 0 0 0。
有3个性质
- 除了根节点外,其它节点最多只有一个子节点。
- 如果节点 u u u的编号小于 v v v的编号,即 u < v u<v u<v,它们的父亲 p u p_u pu和 p v p_v pv的关系为 p u ≤ p v p_u\le p_v pu≤pv。
- 1 1 1固定为根节点的一个子节点。
如果我们把根节点下的各个子树称为链,每次可以询问 a a a和 b b b是否不在同一条链上
花费最多 2 n − 6 2n-6 2n−6次询问,找出除根以外的节点的父节点
思路
我们称根节点的所有子节点为开始节点,可以得到三个推论:
-
开始节点是连续的,即开始节点的编号为 [ 1 , m ] [1, m] [1,m]。
假设其不连续,设不连续的那个点编号为 x x x,则$p_x \in [1,x-1] ,可知 ,可知 ,可知p_{x+1}=0 ,则 ,则 ,则p_{x+1}\lt p_{x}$,违反了性质2
-
每条链上的点随深度递增的。
-
综合上面两个性质,我们可以得出,同一深度的点,所在链开始节点小的点的编号小于开始节点大的点的编号
据此,我们可以很轻易的想出询问方式。
首先枚举 2 ∼ n − 1 2\sim n-1 2∼n−1与 1 1 1一起进行询问,如果得到的是 1 1 1说明该点也是开始节点。如果询问 1 , m 1,m 1,m得到的是 0 0 0,说明开始节点为 [ 1 , m − 1 ] [1,m-1] [1,m−1],且由推论3可得 p m = 1 p_m=1 pm=1。
接着我们使用类似 bfs \text{bfs} bfs的思路枚举剩下的节点 i ∈ [ m + 1 , n − 1 ] i\in[m+1,n-1] i∈[m+1,n−1],我们设 i i i可能的父节点为 j j j, j j j初始为2。如果询问 i , j i,j i,j得到的是 0 0 0,说明 p i = j p_i=j pi=j, i i i和 j j j都要加一,因为一个节点最多只能由一个子节点,否则 j j j加一。
代码实现
int query(int a, int b)
{
printf("? %d %d\n", a, b);
fflush(stdout);
int x;
scanf("%d", &x);
return x;
}
void answer(const vector<int> &v)
{
printf("!");
for (int i = 1; i < v.size(); i++)
{
printf(" %d", v[i]);
}
printf("\n");
fflush(stdout);
}
void solve()
{
int n;
scanf("%d", &n);
vector<int> p(n);
int i = 1;
int m = 2;
while (query(1, m))
{
p[m] = 0;
m++;
}
p[m] = 1;
int j = 2;
for (int i = m + 1; i < n; i++)
{
while (query(i, j))
{
j++;
}
p[i] = j++;
}
answer(p);
}
这场掉大分。
B 题卡了好久,
C 题忘记考虑最小值只有一个的情况,连wa五发,
D 一开始没看太明白题意,先去看E了
E 一开始以为是高斯消元求解线性方程组,设每个位置的操作次数都为 x i x_i xi,每个位置的值则为 T = x i − 1 + x i ∗ 2 + x i + 1 + a i T=x_{i-1} +x_i*2+ x_{i+1} +a_i T=xi−1+xi∗2+xi+1+ai,然后按这个思路搞了半天,突然发现时间复杂度不对。然后就一直在找快速求稀疏矩阵的方法,没找到。赛后才发现不是求解线性方程组。
E题官方题解没太看明白,看有没有时间补吧,最近要做课设
今天的题图就选铃兰妈了,庆祝一下送的十连出了忍冬