Codeforces Round #672 (Div. 2)
比赛链接:https://codeforces.com/contest/1420
A. Cubes Sorting
题意
给出一个大小为 $n$ 的数组 $a$,每次只可以交换相邻的两个元素,最多交换 $\frac{n \cdot (n-1)}{2}-1$ 次,判断能否将数组变为非递减序。
题解一
交换次数最多为 $\frac{n \cdot (n-1)}{2}$,此时数组为严格递减序,即 $a_1 > a_2 > \dots > a_{n - 1} > a_n$,从小到大每个元素依次需要交换 $n-1,n-2,\dots,1,0$ 次,除此外总的交换次数一定会小于 $\frac{n \cdot (n-1)}{2}$ 。
代码
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { int n; cin >> n; vector<int> a(n); for (int i = 0; i < n; i++) cin >> a[i]; if (is_sorted(a.begin(), a.end(), greater<int>()) and unique(a.begin(), a.end()) == a.end()) cout << "NO" << "\n"; else cout << "YES" << "\n"; } return 0; }
题解二
利用单调递减栈计算之前有多少个元素大于当前元素,即为当前元素需要交换的次数。
代码
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { int n; cin >> n; int cnt = 0; stack<int> stk; for (int i = 0; i < n; i++) { int x; cin >> x; while (stk.size() and stk.top() <= x) stk.pop(); cnt += stk.size(); stk.push(x); } cout << (cnt <= 1LL * n * (n - 1) / 2 - 1 ? "YES" : "NO") << "\n"; } return 0; }
B. Rock and Lever
题意
给出一个大小为 $n$ 的数组 $a$,计算满足:
- $i < j$
- $a_i \ \& \ a_j \ge a_i \oplus a_j$
的二元组 $(i,j)$ 的数目。
题解
只有当 $a_i$ 与 $a_j$ 二进制下的最高位相同时才满足条件,记录每一最高位的元素个数,答案即 $\sum_{i=0}^{31}C_i^2$ 。
代码
#include <bits/stdc++.h> int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { int n; cin >> n; long long ans = 0; int cnt[32] = {}; for (int i = 0; i < n; i++) { int x; cin >> x; ans += cnt[__lg(x)]++; } cout << ans << "\n"; } return 0; }
C2. Pokémon Army (hard version)
题意
给出一个大小为 $n$ 的数组 $a$,计算 $a$ 的最大子序列交错和,之后交换 $q$ 对元素,计算每次交换后的最大子序列交错和。
题解
以下标为横坐标,值为纵坐标,最大序列交错和即为 峰顶 - 谷底 + 峰顶 - 谷底 ……
每次交换只会改变两个交换元素及相邻元素是否为峰底的情况,对这最多 $6$ 个元素重新计算即可。
代码
#include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin >> t; while (t--) { int n, q; cin >> n >> q; vector<int> a(n + 2); for (int i = 1; i <= n; i++) cin >> a[i]; long long ans = 0; auto add = [&](int x, int c) { if (x == 0 or x == n + 1) return; if (a[x - 1] <= a[x] and a[x] >= a[x + 1]) ans += a[x] * c; if (a[x - 1] >= a[x] and a[x] <= a[x + 1]) ans -= a[x] * c; }; for (int i = 1; i <= n; i++) add(i, 1); cout << ans << "\n"; while (q--) { int l, r; cin >> l >> r; for (int i = -1; i <= 1; i++) { add(l + i, -1); if (r + i > l + 1) add(r + i, -1); } swap(a[l], a[r]); for (int i = -1; i <= 1; i++) { add(l + i, 1); if (r + i > l + 1) add(r + i, 1); } cout << ans << "\n"; } } return 0; }
D. Rescue Nibel!
题意
给出 $n$ 盏灯的亮灯区间,计算有多少种选择使得同一时刻至少有 $k$ 盏灯是亮着的。
题解
将区间按照左端点从小到大排序,每次记录之前访问区间的右端点,利用优先队列或集合删除小于当前区间的左端点的右端点,此时余下的右端点的个数即为可以与当前灯在同一时刻亮着的灯的个数,选择个数为 $C_i^{k-1}$ 。
代码
#include <bits/stdc++.h> using namespace std; constexpr int N = 1e6 + 100; constexpr int MOD = 998244353; int fac[N], inv[N]; int binpow(int a, int b) { int res = 1; while (b) { if (b & 1) res = 1LL * res * a % MOD; a = 1LL * a * a % MOD; b >>= 1; } return res; } int C(int n, int m){ if(m < 0 or m > n) return 0; return 1LL * fac[n] * inv[m] % MOD * inv[n - m] % MOD; } void Init(){ fac[0] = 1; for (int i = 1; i < N; i++) fac[i] = 1LL * fac[i - 1] * i % MOD; inv[N - 1] = binpow(fac[N - 1], MOD - 2); for (int i = N - 2; i >= 0; i--) inv[i] = 1LL * inv[i + 1] * (i + 1) % MOD; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); Init(); int n, k; cin >> n >> k; vector<pair<int, int>> a(n); for (int i = 0; i < n; i++) cin >> a[i].first >> a[i].second; sort(a.begin(), a.end()); long long ans = 0; priority_queue<int, vector<int>, greater<int>> pque; for (int i = 0; i < n; i++) { while (pque.size() and pque.top() < a[i].first) pque.pop(); (ans += C(pque.size(), k - 1)) %= MOD; pque.push(a[i].second); } cout << ans << "\n"; return 0; }