Codeforces Round 967 (Div. 2) A~C 题解(未完待续)
唐完了,B 构造不会,C 交互不会,整场爆切 \(1\) 题喜提 \(-115\) rating 强势上灰!我还会什么?
I. 2001A - Make All Equal
先找出答案的下界。设众数为 \(x\),其出现的次数为 \(\operatorname{cnt}(x)\)。由于每次操作只能删除一个数,答案不可能小于 \(n - \operatorname{cnt}(x)\)。
下面证明这个下界是可以达到的。用 \(y\) 代表所有的非众数,那么在非众数被删光之前,序列可以表示成 \(x \dots x, y \dots y, \dots\) 之类 \(x\) 与 \(y\) 的连续段交替出现的形式。只需证明一定存在一对相邻元素(这里的相邻指环上的相邻)满足前者小于后者即可。
反证法,如果不存在,则 \(x > y > \cdots\)。由于序列是一个环,所以可以导出 \(x > x\),这是不可能的,故得证。
II. 2001B - Generate Permutation
我是傻逼,我没做出来这题
先考察满足要求的序列有什么性质。可以发现(事实上,我并没有发现),对于从左往右的打字员来说,假设他已经打出了 \(x\),要打 \(x + 1\),只有 \(\operatorname{pos}_x > \operatorname{pos}_{x+1}\) 时他才要按一次回车(其中 \(\operatorname{pos}_x\) 表示 \(x\) 的下标)。因此他按回车的次数 \(c_1 = \sum_{i=1}^{n-1} [\operatorname{pos}_x > \operatorname{pos}_{x+1}]\)。
(这里应该模拟一下打字的过程才能更顺理成章地发现这个结论:假设我已经打出了 \(x\),要打 \(x + 1\),我能直接移动然后打出来,还是必须得回车?显然,只有 \(x + 1\) 在 \(x\) 前面时才要回车,这就对应了 \(\operatorname{pos}_x > \operatorname{pos}_{x+1}\)。这样想的话,这个结论并不难得出,但是我为什么没想到呢?)
同样的,对于从右往左的打字员,只有 \(\operatorname{pos}_x < \operatorname{pos}_{x+1}\) 时他才要按一下回车,因此他按回车的次数 \(c_2 = \sum_{i=1}^{n-1} [\operatorname{pos}_x < \operatorname{pos}_{x+1}]\)。
合法的序列应该满足 \(c_1 = c_2\)。显然,对于 \(x\) 和 \(x + 1\),要么 \(\operatorname{pos}_x > \operatorname{pos}_{x+1}\) 成立,要么 \(\operatorname{pos}_x < \operatorname{pos}_{x+1}\) 成立,所以 \(c_1 + c_2 = n - 1\),因此 \(c_1 = c_2 = \dfrac{n-1}{2}\)。这就说明 \(n\) 是偶数时一定没有合法方案。
\(n\) 是奇数时,可以手模出许多合法的方案。以 \(n = 7\) 为例,一种好想的思路是:先用一堆降序的数把 \(c_1\) 给占满,比如 \(7, 6, 5, 4\)。那么剩下的数必须是升序的,否则会让 \(c_1\) 变大。直接填进去即可: \(1, 2, 3, 7, 6, 5, 4\) 就是可行的。(你会发现 \(7, 6, 5, 4, 1, 2, 3\) 是不可行的,因为 \((3, 4)\) 这一对让 \(c_1\) 变大了)。
因此,\(n\) 是奇数时, \({\color{blue}1, 2, \dots, \left\lfloor \dfrac{n-1}{2} \right\rfloor,} {\color{red}n, n-1, \dots, \left\lfloor \dfrac{n+1}{2} \right\rfloor}\) 是一个合法的方案。这里用不同颜色区分构造时分别填入的两段。
void solve()
{
int n;
cin >> n;
if((n & 1) == 0)
{
cout << -1 << endl;
return;
}
vector<int> ans(n);
iota(ans.begin(), ans.end(), 1);
reverse(ans.begin() + (n/2), ans.end());
for(int x: ans) cout << x << ' ';
cout << endl;
}
III. 2001C - Guess The Tree
下面是官方题解的思路。我自己想不出来这个做法,所以不知道用什么语言来引入。
把点划分为两个点集 \(A\) 和 \(B\),\(A\) 中的点是知道其内部边集的点,\(B\) 中的点是不知道的。
初始时,任意把一个点加入 \(A\) 中(例如 \(1\)),\(B\) 是剩下的 \(n-1\) 个点。
我们的策略是:每次把一个新的点加入到 \(A\) 中。这个点必须和原来 \(A\) 中的某个点有连边,否则根本无法把它加入 \(A\)。
任选点 \(u \in A\),\(v \in B\),查询 \(u, v\) 的中点 \(x\)。如果 \(x \in A\),则令 \(u \gets x\),否则令 \(v \gets x\)。这里的要义是,时刻保证 \(u \in A\) 且 \(v \in B\)。最终当 \(x = u\) 时,就表明 \(u\),\(v\) 之间有边相连,可以更新答案的边集。由于我们保证了 \(v \in B\),所以可以把 \(v\) 加入到 \(A\) 中,并在 \(B\) 中删掉 \(v\)。
这里的查找类似二分,每次找到一个新点的查询次数不会超过 \(\log n\),总次数不超过 \(n \log n\),可以通过此题。
但是我不知道我自己该如何想出这些东西