LOJ3140 [COI2019] TENIS
神奇滴很的结论题。
若选手 \(u\) 能够在至少一个场地战胜选手 \(v\),则连一条 \((u, v)\) 的有向边。选手 \(u\) 能够获胜即从点 \(u\) 出发能到达其他所有结点。
我们把强连通分量缩成一个点,由于该图类似竞赛图,容易发现缩完点后构成了一条有向链,每一个结点都向它后面的所有结点连边。
显然,有且仅有链头的强连通分量内的点能够获胜。
考虑同一个强连通分量内的点对应到给出的三个数组上有什么性质。
手玩发现,强连通分量纵向地划分了这三个数组。
给三个数组间相同的数连边,可以发现划分的位置就是没有边经过的位置。
考虑证明该性质。
对于链上相邻两个强连通分量,靠前的强连通分量中的每个点一定在三个数组中都位于靠后的强连通分量中每个点的前面。即它们在链上一定会形成一个划分。
同时,不难证明每一个划分两侧的点都属于不同的强连通分量。
所以该性质得证。
所以我们只需找出三个数组中第一个没有被边经过的间隙即可回答询问。
由于带修,所以用线段树实现。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
int n, m, rk[MAXN][3], mn[MAXN * 4], tag[MAXN * 4];
inline int ls(int p) { return p << 1; }
inline int rs(int p) { return p << 1 | 1; }
inline void push_up(int p) { mn[p] = min(mn[ls(p)], mn[rs(p)]); }
inline void get_down(int p, int x) { mn[p] += x, tag[p] += x; }
inline void push_down(int p) {
if (!tag[p]) return;
get_down(ls(p), tag[p]);
get_down(rs(p), tag[p]);
tag[p] = 0;
}
void update(int p, int l, int r, int ql, int qr, int x) {
if (ql <= l && r <= qr) {
get_down(p, x);
return;
}
push_down(p);
int mid = (l + r) >> 1;
if (ql <= mid) update(ls(p), l, mid, ql, qr, x);
if (qr > mid) update(rs(p), mid + 1, r, ql, qr, x);
push_up(p);
}
int find(int p, int l, int r) {
if (l == r) return l;
push_down(p);
int mid = (l + r) >> 1;
if (mn[ls(p)]) return find(rs(p), mid + 1, r);
return find(ls(p), l, mid);
}
void add(int x, int y) {
int l = *min_element(rk[x], rk[x] + 3), r = *max_element(rk[x], rk[x] + 3);
if (l != r) update(1, 1, n, l, r - 1, y);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int j = 0, x; j < 3; ++j)
for (int i = 1; i <= n; ++i) cin >> x, rk[x][j] = i;
for (int i = 1; i <= n; ++i) add(i, 1);
while (m--) {
int typ;
cin >> typ;
if (typ == 1) {
int x;
cin >> x;
cout << (rk[x][0] <= find(1, 1, n) ? "DA" : "NE") << "\n";
} else {
int p, u, v;
cin >> p >> u >> v, --p;
add(u, -1), add(v, -1);
swap(rk[u][p], rk[v][p]);
add(u, 1), add(v, 1);
}
}
return 0;
}