HDU6756 Finding a MEX
阿新 • • 發佈:2020-07-29
原題連結
看到\(\operatorname{mex}\),一個顯然的想法就是根號分治。我們設\(d_u\)為點\(u\)的度數,再設一個合適正整數\(L\),代表根號分治的臨界值。將所有的點分為兩個集合\(S_1,S_2\),使得\(\forall u\in S_1,d_u\le L;\forall u\in S_2,d_u>L\)。有一個顯然的性質是\(|S_2|\le \big\lfloor \dfrac{2\times n}{L}\big\rfloor\)。
先考慮查詢。對於所有\(S_1\)中的點,因其鄰居的個數最多是\(L\),所以珂以暴力列舉鄰居算\(\operatorname{mex}\)
先不談這個資料結構要實現哪些東西,我們來看看修改。修改是單點修改。一個點的修改只會影響到它周邊鄰居的答案。設點\(u\)的鄰居的集合為\(C_u\)。對於集合\(S_1\cap C_u\)中的所有點,珂以不用管它們,因為我們在詢問時是暴力列舉計算的。所以我們只要考慮集合\(S_2\cap C_u\)
所以這個資料結構要支援的就是:1. 插入; 2. 刪除; 3. 查詢全域性\(\operatorname{mex}\)。那麼問題來了,這個資料結構是啥呢?顯然平衡樹啊!每個節點維護兩個值:\(val, cnt\)。這樣詢問時就直接二分,判斷ls->size
是否等於this->cnt
即珂。
所以查詢時的時間複雜度為\(O(L+\log n)\)
Ofast
)。
程式碼:
#include <bits/stdc++.h>
using namespace std;
constexpr int _I_Buffer_Size = 32 << 20;
static char _I_Buffer[_I_Buffer_Size];
char *_I_pos = _I_Buffer;
void read(int32_t *n) {
while (*_I_pos < 48) _I_pos++;
*n = *_I_pos++ - '0';
while (*_I_pos > 47)
*n = *n * 10 + (*_I_pos++ - '0');
}
constexpr int Maxn = 1e5 + 5, BL = 1024;
int n, m, q;
int a[Maxn];
vector<int> g[Maxn];
int deg[Maxn];
__attribute__((__always_inline__)) __inline
void add_edge(int eu, int ev) {
g[eu].push_back(ev), deg[ev]++;
}
bool appear[Maxn];
namespace treap {
constexpr int Maxs = ::Maxn * 127;
static mt19937 rng(40113143);
int ls[Maxs], rs[Maxs], fix[Maxs], size[Maxs], tot;
int val[Maxs], cnt[Maxs];
__attribute__((__always_inline__)) __inline
void init_treap() {
tot = 0;
ls[0] = rs[0] = 0;
size[0] = 0, fix[0] = 0;
val[0] = cnt[0] = 0;
}
__attribute__((__always_inline__)) __inline
int newnode(int v) {
++tot;
ls[tot] = rs[tot] = 0;
fix[tot] = rng();
size[tot] = 1;
val[tot] = v;
cnt[tot] = 1;
return tot;
}
__attribute__((__always_inline__)) __inline
void pushup(int p) {
size[p] = size[ls[p]] + size[rs[p]] + 1;
}
int join(int l, int r) {
if (!l || !r) return l | r;
if (fix[l] < fix[r]) {
rs[l] = join(rs[l], r);
pushup(l);
return l;
}
else {
ls[r] = join(l, ls[r]);
pushup(r);
return r;
}
}
void split_s(int p, int s, int &l, int &r) {
if (!p) l = r = 0;
else {
int lsize = size[ls[p]] + 1;
if (lsize <= s) {
l = p;
split_s(rs[p], s - lsize, rs[l], r);
pushup(l);
}
else {
r = p;
split_s(ls[p], s, l, ls[r]);
pushup(r);
}
}
}
void split_v(int p, int v, int &l, int &r) {
if (!p) l = r = 0;
else {
if (val[p] <= v) {
l = p;
split_v(rs[p], v, rs[l], r);
pushup(l);
}
else {
r = p;
split_v(ls[p], v, l, ls[r]);
pushup(r);
}
}
}
void insert(int &p, int x) {
int A, B, C;
split_v(p, x - 1, A, B);
split_v(B, x, B, C);
if (size[B] > 0) cnt[B]++;
else B = newnode(x);
p = join(join(A, B), C);
}
void erase(int &p, int x) {
int A, B, C;
split_v(p, x - 1, A, B);
split_v(B, x, B, C);
if (--cnt[B] == 0) B = 0;
p = join(join(A, B), C);
}
int mex(int p) {
int c = 0;
while (p) {
int lsize = size[ls[p]] + 1;
if (lsize + c == val[p] + 1) {
c += lsize;
p = rs[p];
}
else {
p = ls[p];
}
}
return c;
}
} // namespace treap
using namespace treap;
int root[Maxn];
void re_clear() {
for (int i = 1; i <= n; ++i) {
g[i].clear();
deg[i] = 0;
}
memset(root, 0, (n + 1) << 2);
}
void Main() {
read(&n), read(&m);
for (int i = 1; i <= n; ++i) {
read(a + i);
if (a[i] > n) a[i] = n + 1;
}
for (int i = 1; i <= m; ++i) {
int u, v;
read(&u), read(&v);
add_edge(u, v);
add_edge(v, u);
}
static const auto cmp_node =
[&](int x, int y)->bool {
return deg[x] > deg[y];
};
for (int i = 1; i <= n; ++i)
sort(g[i].begin(), g[i].end(), cmp_node);
init_treap();
for (int u = 1; u <= n; ++u)
if (deg[u] > BL) {
for (int &v: g[u]) treap::insert(root[u], a[v]);
}
read(&q);
while (q--) {
int op, u, x;
read(&op), read(&u);
if (op == 1) {
read(&x);
if (x > n) x = n + 1;
for (int &v: g[u]) {
if (deg[v] <= BL) break;
treap::erase(root[v], a[u]);
treap::insert(root[v], x);
}
a[u] = x;
}
else {
if (deg[u] <= BL) {
for (int &v: g[u])
appear[a[v]] = true;
for (int i = 0; ; ++i)
if (!appear[i]) {
printf("%d\n", i);
break;
}
for (int &v: g[u])
appear[a[v]] = false;
}
else {
printf("%d\n", treap::mex(root[u]));
}
}
}
re_clear();
}
int main() {
fread(_I_Buffer, 1, _I_Buffer_Size, stdin);
int tests;
read(&tests);
while (tests--) Main();
return 0;
}