题解:CF313E Ilya and Two Numbers

· · 题解

不会贪心,来个简单暴力的 bitset 解法。

为了让答案的字典序尽可能大,我们先想办法凑出尽量多的 (m - 1)。令 a_i 表示第一个序列里的 i 还剩几个,b_i 表示第二个序列里的 i 还剩几个。想象两个链表,第一个链表装的是 a_0 \to a_1 \to \cdots \to a_{m - 1},第二个链表装的是 b_{m - 1} \to b_{m - 2} \to \cdots \to b_0,那么只要两个链表的第 i 个节点都非 0,我们就能把对应的元素匹配上,凑出若干个 (m - 1)

需要注意的是,如果我们每轮都枚举链表的每个节点,复杂度是 $\mathcal{O}(m^2)$ 的,所以我们每轮只能枚举两个链表均非 $0$ 的下标。怎么快速找出这些下标呢?用两个 `bitset` 维护两个链表哪些下标非 $0$,两者 `&` 起来,就能找到哪些下标同时非 $0$ 了。标准库里的 `bitset` 不支持快速找到第一个 $1$ 在哪,所以我们得手写 `bitset`。 由于每次枚举一对非 $0$ 的节点,都能匹配至少一对数,所以至多匹配 $n$ 次,复杂度 $\mathcal{O}(\frac{(n + m)m}{w})$,其中 $w = 64$ 是计算机字长。 ```c++ #include <bits/stdc++.h> #define MAXN ((int) 1e5) using namespace std; int n, m, A[2][MAXN + 5]; // 手写 bitset struct Bitset { const int W = 64; int len; typedef unsigned long long ull; vector<ull> vec; Bitset(int len): len(len) { vec.resize((len + W - 1) / W, 0); } void set(int pos) { vec[pos / W] |= 1ULL << (pos & (W - 1)); } void unset(int pos) { vec[pos / W] &= ~(1ULL << (pos & (W - 1))); } // bitset 左移一位 void shift() { int b = vec[0] & 1; for (int i = 0; i < vec.size(); i++) { int t = vec[i] & 1; vec[i] >>= 1; if (t && i > 0) vec[i - 1] |= 1ULL << (W - 1); } if (b) vec[vec.size() - 1] |= 1ULL << ((len - 1) & (W - 1)); } // 两个 bitset & 起来 void join(Bitset &o) { assert(len == o.len); for (int i = 0; i < vec.size(); i++) vec[i] &= o.vec[i]; } // 找到第 bgn 位右边最近的 1 int findFirst(int bgn) { int bias = bgn & (W - 1); for (int i = bgn / W; i < vec.size(); i++) { ull x = vec[i] >> bias; x &= -x; if (x) return i * W + __lg(x) + bias; bias = 0; } return -1; } }; int main() { scanf("%d%d", &n, &m); for (int k = 0; k < 2; k++) for (int i = 1; i <= n; i++) { int x; scanf("%d", &x); A[k][x]++; } Bitset X(m), Y(m); // 第一个链表从 0 到 m - 1 for (int i = 0; i < m; i++) if (A[0][i]) X.set(i); // 第二个链表从 m - 1 到 0 for (int i = 0; i < m; i++) if (A[1][i]) Y.set(m - 1 - i); // 从 m - 1 开始凑 for (int v = m - 1; v >= 0; v--) { Bitset Z = X; Z.join(Y); int idx = 0; while (true) { // 每次只访问两个链表同时非 0 的节点 idx = Z.findFirst(idx); if (idx < 0) break; int iidx = (v - idx + m) % m; int t = min(A[0][idx], A[1][iidx]); for (int i = 1; i <= t; i++) printf("%d ", v); if ((A[0][idx] -= t) == 0) X.unset(idx); if ((A[1][iidx] -= t) == 0) Y.unset(idx); idx++; } // 每轮把第二个链表左移一位 Y.shift(); } printf("\n"); return 0; } ```