2026 年 ACM 暑假集训

2026 年 ACM 暑假集训

2026 牛客暑期多校训练营 1

F. Permutation Generation

给定 \(n \in \mathbb{N}^*\),记 \(N = \mathbb{N} \cap [0, n)\)。 对于 \(N\) 的一个排列 \(p = (p_i)_{i = 0}^{n - 1}\),记

\f(p) = \\sum_{i = 0}\^{n - 1} \\sum_{j = i + 1}\^{n - 1}(p_j - p_i). \\

现在给出 \(N\) 的一个排列 \(p\) 和 \(k,v \in N\),你需要求出 \(N\) 的一个排列 \(q = (q_i)_{i = 0}^{n - 1}\),使得以下条件都成立:

  • \(q_k = v\)。
  • \(f(p) \equiv f(q) \pmod{n}\)。

或报告无解。

保证 \(n \leq 2 \times 10^5\)。

\(f\) 是容易化简的:

\f(p) = \\sum_{i = 0}\^{n - 1} p_i (i - (n - 1 - i)) = \\sum_{i = 0}\^{n - 1} p_i (2i - n + 1). \\

如果再利用上 \(p\) 是排列这一性质的话,还可以进一步化简为

\f(p) = 2 \\sum_{i = 0}\^{n - 1} p_i i - \\dfrac{1}{2} n (n - 1)\^2. \\

我和一位队友都注意到,如果交换 \(p\) 的第 \(x\) 和第 \(y\) 项得到 \(p'\),会使其函数值变化

\f(p') - f(p) = -2 (y - x) (p_y - p_x). \\

因此一直在考虑,要么从 \(p\) 交换出 \(q_k = v\) 后再想办法消除其影响,或直接从顺序交换出 \(q_k = v\) 后再凑出 \(f(q) \equiv f(p) \pmod{n}\)。我甚么办法也没想出来,队友倒是搞出了一种,但没有覆盖到所有情况,导致 WA。

后来我们通过枚举小的数据,观察到都是有解的,因此猜测必定有解,但没有甚么用。

当另一位队友终于写完 J 题大模拟后再看此题时,观察枚举的数据时注意到每个 \(n\) 都有答案可以通过 circular shift(循环移动?)得到,遂出正解。

这里证明一下正确性:设将 \(p\) 循环左移一位得到 \(p'\),有

\\\begin{align} f(p') \& = \\sum_{i = 0}\^{n - 1} p_i (2(i - 1) - n + 1) + 2np_0 \\\\ \& = \\sum_{i = 0}\^{n - 1} p_i (2i - n + 1) - 2 \\sum_{i = 0}\^{n - 1} p_i + 2 n p_0 \\\\ \& = f(p) - n (n - 1) + 2 n p_0 \\\\ \& \\equiv f(p) \\pmod{n}. \\end{align} \\

所以只需通过循环移动使第 \(k\) 位为 \(v\) 即可。时间复杂度 \(O(1)\)。

后来 Gemini 告诉我们,在出现 \(\bmod n\) 时应当优先考虑 circular shift。这是我们没有经验。

cpp 复制代码
inline void solve() {
	uint32_t n, idx, val;
	std::cin >> n >> idx >> val;
	std::vector<uint32_t> perm(n);
	uint32_t pos;
	for (uint32_t i = 0; i < n; ++i) {
		std::cin >> perm[i];
		if (perm[i] == val) pos = i;
	}
	uint32_t lsh = pos - idx + n;
	if (lsh >= n) lsh -= n;
	for (uint32_t i = lsh; i < n; ++i) {
		std::cout << perm[i] << ' ';
	}
	for (size_t i = 0; i < lsh; ++i) {
		std::cout << perm[i] << ' ';
	}
	std::cout << '\n';
}

G. Precision Error?!

给定 \(n \in \mathbb{N}^*\),需要找出一个点集 \(S \subset \mathbb{R}^3\) 使得

  • \(|S| \leq 2 n + 2\)。
  • \((\forall P \in S)\ (|\{Q \in S: \Vert\vec{PQ}\Vert_2 = 1\}| = n)\)。

为了允许浮点误差,取精度 \(\epsilon = 0.01\), \(S\) 需要满足

  • \((\forall P,Q \in S)\ (P = Q \lor \Vert\vec{PQ}\Vert_2 > \epsilon)\)。
  • \(|S| \leq 2 n + 2\)。
  • \((\forall P \in S)\ (|\{Q \in S: \Vert\vec{PQ}\Vert_2 \in (1 - \epsilon, 1 + \epsilon)\}| = n)\)。

保证 \(n \leq 100\)。有 \(T \leq 100\) 组测试。

看到题解真气笑了。我就说这个 \(\epsilon\) 怎么这么大,别的题都是 \(10^{-5}\)、\(10^{-6}\),这里来个 \(10^{-2}\),他妈的,果然有蹊跷。

注意到这里的 \(\epsilon\) 相当大,所以我们可以通过构造两部分点,使得同一部分的点之间非常近但不比 \(\epsilon\) 近,而不同部分的点之间的距离都几乎为 \(1\)。

对于平面 \(z = 0\) 上的一个点,在平面 \(z = 1\) 中有一个半径为 \(r = \sqrt{(1 + \epsilon)^2 - 1^2} \approx 0.1418 > 14 \epsilon\) 的圆,这个圆内的点到该点的距离都在 \([1, 1 + \epsilon)\)。这实际上是因为 \(\sqrt{1 + \left(\sqrt{2 \epsilon}\right)^2} = 1 + \epsilon + o(\epsilon^2)\),即圆的半径 \(r \approx \sqrt{2 \epsilon}\)。我们可以在两个平面上各放 \(n\) 个点。为了让两个平面的点互相在对方的容忍范围中,我们需要使它们都位于圆柱 \(x^2 + y^2 \leq \left(\dfrac{1}{2} r\right)^2\) 内(当然,圆心位置实际上是任意的)。

简便起见,考虑将每个平面的点以正方形点阵排布,即用一个 \(\left\lceil \sqrt{n}\right\rceil \times \left\lceil \sqrt{n}\right\rceil\) 点阵排布这 \(n\) 个点。这样的正方形点阵中,设两个点的最近距离(即相邻两点的距离)为 \(d\),则两个点的最远距离为 \(\left(\left\lceil \sqrt{n}\right\rceil - 1\right) \cdot \sqrt{2} d\)。我们希望

\\\begin{cases} d \> \\epsilon \&\& \\text{不重合} \\\\ \\left(\\left\\lceil \\sqrt{n}\\right\\rceil - 1\\right) \\cdot \\sqrt{2} d \< 2 \\cdot \\dfrac{1}{2} r \&\& \\text{在圆内} \\end{cases}, \\

解该不等式组得

\d \\in \\left(\\epsilon, \\dfrac{r}{\\sqrt{2} \\left(\\left\\lceil \\sqrt{n}\\right\\rceil - 1\\right)}\\right) \\

由 \(n \leq 100\) 得上界

\\\dfrac{r}{\\sqrt{2} \\left(\\left\\lceil \\sqrt{n}\\right\\rceil - 1\\right)} \\geq \\dfrac{r}{9\\sqrt{2}} \\approx 0.01114 \> 1.1 \\epsilon, \\

所以完全是可以的。

cpp 复制代码
constexpr double D = 0.011;
constexpr size_t N = 100, S = 10; // sqrt(N)
std::array<std::pair<double, double>, N> points;

inline void preprocess() {
	for (size_t i = 0; i < S; ++i) {
		for (size_t j = 0; j < S; ++j) {
			points[i * S + j] = {D * i, D * j};
		}
	}
	std::cout << std::fixed << std::setprecision(9);
}

inline void solve() {
	size_t n;
	std::cin >> n;
	std::cout << (n << 1) << '\n';
	for (int z = 0; z < 2; ++z) {
		for (size_t i = 0; i < n; ++i) {
			std::cout << points[i].first << ' ' << points[i].second << ' ' << z << '\n';
		}
	}
}

时间复杂度:\(O(n)\) 预处理,\(O(1)\) 查询。