题解:P17243 [IOI 2026] 课堂游戏 / Classroom Game

大大大神题来的。

先考虑 \(C=3\) 怎么做。受置换的影响,我们记录信息时需要把学生编号加进去。考虑直接记录 \((R,i,d_i)\) 表示这个信息在第 \(R\) 步时记录,学生 \(i\)\(d_i\) 步举手。在第 \(R\) 步的时候,对于所有没举手的学生,我们直接把对应数组改成 \((R,i,d_i)\),若尚未举手,则令 \(d_i=63\)。那么在第 \(R+1\) 步的时候,只需要找出所有第一项不为 \(R\) 的学生,这些学生恰好就是 \(d_i=R\) 的学生,这样就可以得到上一轮的举手信息了。最终只需要用同样的方法得到 \(d_i=M-1\) 的学生,然后把 \(d_i=63\) 的学生改成 \(d_i=-1\) 即可。这个做法可以获得 \(75\) 分。

尝试进一步压缩到 \(C=2\)。感觉上只有 \(R\) 这一维可以压掉,因此考虑直接记录 \((i,d_i)\)。观察到 \(C=3\) 的做法中记录 \(R\) 是为了区分新旧信息,而现在为了把 \(R\) 压掉,考虑对于记录下来的正确信息 \((i,d_i)\),我们始终不去修改它们。

对于第 \(R\) 步,考虑如何利用可以修改的位置向下一轮传递 \(d_i=R\) 的学生的信息。把学生分成三类:

\[\begin{align*} X&=\{i\mid d_i<R\}\\ Y&=\{i\mid d_i=R\}\\ Z&=\{i\mid d_i>R\lor d_i=-1\} \end{align*} \]

假设当前记录了 \(c\) 条正确信息,其中有 \(c'\) 条被置换到了 \(Y\) 的位置上,那么还剩下 \(|X|+|Z|-(c-c')\) 个位置。不妨假设我们已经得到了 \(d_i=R-1\) 的学生,而前一轮中不一定把这些学生的信息都记录了下来,因此这一轮还需要补上 \(|X|-c\) 条信息。此时剩下 \(|X|+|Z|-(c-c')-(|X|-c)=|Z|+c'\) 个空位,于是我们至少有 \(|Z|\) 个可用的空位。

\(|Y|\leq |Z|\),我们显然可以直接挑 \(|Y|\) 个空位记录信息。

\(|Y|>|Z|\),不妨转而记录 \(Z\),这样下一轮可以直接通过 \((Y\cup Z)\setminus Z\) 得到 \(Y\)。因此考虑建立 \(Z\)\(Y\) 中大小为 \(|Z|\) 的子集之间的的双射,在第 \(R\) 步中,我们把映射到的子集中的信息记录到空位上,在第 \(R+1\) 步中再把这些信息映射回 \(Z\) 即可。

\(S=Y\cup Z\),问题转化为:已知 \(2|Z|<|S|\),如何把 \(S\) 的每个 \(|Z|\) 元子集双射到与它不交的 \(|Z|\) 元子集。不交看起来不太好研究,可以进一步转化成:把 \(S\) 的每个 \(|Z|\) 元子集 \(T\) 双射到 \(|S|-|Z|\) 元子集 \(f(T)\),满足 \(T\subseteq f(T)\)

考虑一个很神的的构造:把 \(\in T\) 的位置标成左括号,\(\notin T\) 的位置标成右括号。这样得到一个包含 \(|Z|\) 个左括号和 \(|S|-|Z|\) 个右括号的括号串。对这个括号串做贪心括号匹配,未匹配的部分必然形如 \(\texttt{)}^{a+d}\texttt{(}^a\),其中 \(d=|S|-2|Z|\)。考虑直接把未匹配的位置改成 \(\texttt{)}^a\texttt{(}^{a+d}\),这样得到的新串包含 \(|S|-|Z|\) 个左括号和 \(|Z|\) 个右括号,且我们只会把若干个右括号改成左括号,因此新串对应的集合 \(f(T)\) 也是 \(T\) 的超集!这样我们就构造出了这个映射,从 \(f(T)\) 映射回 \(T\) 的方法是一样的。

还剩下一个问题:第 \(R+1\) 步时如何区分上一步是 \(|Y|\leq |Z|\) 还是 \(|Y|>|Z|\)。考虑 \(|Y|<|Z|\) 时我们可以利用多余的空位打一个 \((63,R)\) 的标记,这样就可以和 \(|Y|>|Z|\) 区分开了。而 \(|Y|=|Z|\) 时我们可以直接通过数量关系判断出来,所以也没有问题。

于是我们成功得到了 \(C=2\) 的满分做法。

利用 \(N,M\leq 63\) 的性质把各种东西压成一个 ull 会好些很多。

主要代码
ull trans(int N, ull s, ull x) {
	vector<int> stk;
	for (int i = 0; i < N; ++i) {
		if (~s >> i & 1) continue;
		if (x >> i & 1) stk.emplace_back(i);
		else {
			if (!stk.empty() && (x >> stk.back() & 1)) stk.pop_back();
			else stk.emplace_back(i);
		}
	}

	int c = 0;
	for (int i : stk) c += x >> i & 1;
	for (int i = 0; i < c; ++i) x &= ~(1ull << stk[i]);
	for (int i = c; i < stk.size(); ++i) x |= 1ull << stk[i];
	return x;
}

pair<vector<int>, ull> get(int N, int M, int R, const vector<vector<int>> &A) {
	vector<int> d(N, -1);
	ull CX = (1ULL << N) - 1, Y = 0;
	bool mark = false;
	for (auto &vec : A) {
		if (vec.size() == 2 && vec[0] == 63) {
			if (vec[1] == R) mark = true;
			continue;
		}
		if (vec.empty()) continue;
		if (vec[1] < R) {
			d[vec[0]] = vec[1];
			CX &= ~(1ull << vec[0]);
		} else if (vec[1] == R) {
			Y |= 1ull << vec[0];
		}
	}

	if (mark || __builtin_popcountll(Y) * 2 == __builtin_popcountll(CX)) return {d, Y};
	else return {d, CX ^ trans(N, CX, CX ^ Y)};
}

vector<vector<int>> process_step(int N, int M, int R, vector<int> T, vector<vector<int>> A) {
	vector<int> d(N, -1);

	if (R) {
		auto [nd, S] = get(N, M, R - 1, A);
		nd.swap(d);
		for (int i = 0; i < N; ++i) {
			if (S >> i & 1) d[i] = R - 1;
		}
	}

	ull Y = 0, cur = 0;
	for (int i : T) Y |= 1ull << i;

	for (auto &vec : A) {
		if (vec.size() == 2 && vec[0] != 63) cur |= 1ull << vec[0];
	}
	vector<int> pos;
	for (int i = 0; i < N; ++i) {
		if ((Y >> i & 1) || (A[i].size() == 2 && A[i][0] != 63)) continue;
		A[i].clear();
		pos.emplace_back(i);
	}

	int p = 0;
	for (int i = 0; i < N; ++i) {
		if (d[i] != -1 && (~cur >> i & 1)) A[pos[p++]] = {i, d[i]};
	}

	ull Z = 0;
	for (int i = 0; i < N; ++i) {
		if (d[i] == -1 && (~Y >> i & 1)) Z |= 1ull << i;
	}
	ull CX = Y | Z;
	int y = __builtin_popcountll(Y), z = __builtin_popcountll(Z);
	if (y <= z) {
		for (int i : T) A[pos[p++]] = {i, R};
		if (y < z) A[pos[p++]] = {63, R};
	} else {
		ull S = CX ^ trans(N, CX, Z);
		for (int i = 0; i < N; ++i) {
			if (S >> i & 1) A[pos[p++]] = {i, R};
		}
	}

	return A;
}

vector<int> determine_steps(int N, int M, vector<vector<int>> A) {
	auto [d, S] = get(N, M, M - 1, A);
	for (int i = 0; i < N; ++i) {
		if (S >> i & 1) d[i] = M - 1;
	}
	return d;
}
posted @ 2026-08-15 17:34  P2441M  阅读(14)  评论(0)    收藏  举报