The 2024 ICPC Asia East Continent Online Contest (I) C.Permutation Counting 4

Link: Permutation Counting 4

我的评价是神题,给出两种做法。

方法一

利用线代技巧。

设法构造矩阵 \(A\), 其中 \(A_{ij} = [j \in [l_i, r_i]]\),对所有排列 \(p\),所有的合法答案的数量以下式表示:

\[\sum_{p}\prod_{i = 1}^n A_{ij} \]

上式为行列式的积和式,用 \(\operatorname{perm}(A)\) 表示。

由行列式定义可知:

\[\operatorname{det}(A) = \sum_{p}\operatorname{sign}(p)\prod_{i = 1}^n A_{ij} \]

在模 \(2\) 意义下,正负号同余,因此有:

\[\operatorname{det}(A) \equiv \operatorname{perm}(A) \]

若行列式不为 \(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;
}
posted @ 2024-11-10 21:57  YipChip  阅读(9)  评论(0编辑  收藏  举报