The 2024 ICPC Asia East Continent Online Contest (I) C.Permutation Counting 4
我的评价是神题,给出两种做法。
方法一
利用线代技巧。
设法构造矩阵 \(A\), 其中 \(A_{ij} = [j \in [l_i, r_i]]\),对所有排列 \(p\),所有的合法答案的数量以下式表示:
上式为行列式的积和式,用 \(\operatorname{perm}(A)\) 表示。
由行列式定义可知:
在模 \(2\) 意义下,正负号同余,因此有:
若行列式不为 \(0\),则矩阵 \(A\) 可逆,因此矩阵 \(A\) 是满秩的,我们只用找其中是否有向量与其他向量线性相关(即行能否被其他行表示),这可以通过异或操作实现。
考虑 \(1\) 的位置是连续的,对两组向量异或可以看做两条线段覆盖,存在一组线段可以使得所有位置被覆盖偶数次即可,考虑能否将若干线段连接起来即可,即只需考虑是否存在 \([l_i, r_i + 1]\) 连接后恰好覆盖其他线段,考虑并查集。
时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, p[N];
int find(int x) {
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n + 1; i ++ ) p[i] = i;
int ans = 0;
for (int i = 1; i <= n; i ++ ) {
int l, r;
cin >> l >> r;
if (find(l) == find(r + 1)) ans = 1;
else p[find(l)] = find(r + 1);
}
if (ans) cout << "0\n";
else cout << "1\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T -- ) solve();
return 0;
}
方法二
如果我们思维太迟钝(没有学好线代),按照一般思考流程来解决,发现答案非 \(0\) 即 \(1\),如果我们能发现某一种是好求的,那么另一种可以快速得出。
考虑答案为 \(0\) 的充要条件,无解,或者有解且解为偶数倍,容易发现对两组 \(A = [l_i, r_i]\),\(B = [l_j, r_j]\) 的答案为 \(s, t\),若 \(\{s, t\} \in A, B\) 那么交换 \(s, t\),答案成立,故答案一定为偶数倍。
因此我们考虑能否答案是 \(1\),这是好算的,考虑 \(l_i = 1\) 的情况,如果 \(r_1 < r_2 < \ldots < r_k\),第一个 \([1, r_1]\) 任取,第二个 \([1, r_2]\) 只能从 \([r_1 + 1, r_2]\) 中取,否则存在相交,两者可以交换,不符合条件。
因此我们建立 \(n\) 个堆,向第 \(l_i\) 个堆插入 \(r_i\),从小到大枚举,弹出堆中最小元素的同时向第 \(r_i + 1\) 个堆做启发式合并,判断是否有解即可。
时间复杂度 \(O(n\log^2{n})\),跑不满,很神秘,但是能过。
#include<bits/stdc++.h>
using namespace std;
void solve() {
int n;
cin >> n;
vector<priority_queue<int, vector<int>, greater<int>>> heap(n + 2);
for (int i = 1; i <= n; i ++ ) {
int l, r;
cin >> l >> r;
heap[l].push(r);
}
for (int i = 1; i <= n; i ++ ) {
if (heap[i].empty()) {
return cout << "0\n", void();
}
int t = heap[i].top();
heap[i].pop();
if (!heap[i].empty() && heap[i].top() == t) {
return cout << "0\n", void();
}
if (heap[i].size() > heap[t + 1].size()) {
swap(heap[i], heap[t + 1]);
}
while (heap[i].size()) {
heap[t + 1].push(heap[i].top());
heap[i].pop();
}
}
cout << "1\n";
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T -- ) solve();
return 0;
}