CF2000 A~C 题解

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\),可以通过此题。

但是我不知道我自己该如何想出这些东西

posted @ 2024-08-21 22:37  DengStar  阅读(8)  评论(0编辑  收藏  举报