Finding a MEX
Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 1257 Accepted Submission(s): 218
Problem Description
Given an undirected graph G=(V,E). All vertices are numbered from 1 to N. And every vertex u has a value of Au. Let Su={Av│(u,v)∈E}. Also, F(u) equals MEX(minimum excludant) value of Su. A MEX value of a set is the smallest non-negative integer which doesn’t exist in the set.
There are two types of queries.
Type 1: 1 u x – Change Au to x (0≤x≤109).
Type 2: 2 u – Calculate the value of F(u).
For each query of type 2, you should answer the query.
There are two types of queries.
Type 1: 1 u x – Change Au to x (0≤x≤109).
Type 2: 2 u – Calculate the value of F(u).
For each query of type 2, you should answer the query.
Input
The first line of input contains a single integer T (1≤T≤10) denoting the number of test cases. Each test case begins with a single line containing two integers n (1≤n≤105), m (1≤m≤105) denoting the number of vertices and number of edges in the given graph.
The next line contains n integers and ith of them is a value of Ai (0≤Ai≤109).
The next m lines contain edges of the graph. Every line contains two integers u, v meaning there exist an edge between vertex u and v.
The next line contains a single integer q (1≤q≤105) denoting the number of queries.
The next q lines contain queries described in the description.
The next line contains n integers and ith of them is a value of Ai (0≤Ai≤109).
The next m lines contain edges of the graph. Every line contains two integers u, v meaning there exist an edge between vertex u and v.
The next line contains a single integer q (1≤q≤105) denoting the number of queries.
The next q lines contain queries described in the description.
Output
For each query of type 2, output the value of F(u) in a single line.
Sample Input
1
5 4
0 1 2 0 1
1 2
1 3
2 4
2 5
5
2 2
1 2 2
2 2
1 3 1
2 1
Sample Output
2
2
0
题目大意:给你1e5个点带点权的无向图,1e5次操作,要么更改某点的点权,要么查询某个点周围点集的MEX。思路 :把每个点分成两部分,一个是度数 >= sqrt(N)的点,一个是度数 < sqrt(N)的点,前者我称为大堆,后者我称为小堆,小堆暴力,大堆用数据结构维护(分块的基操)。维护MEX,很容易想到权值线段树or树状数组,注意这里用vector动态开二维的; 查询时二分某个位置,看看前面是不是已经填满;为了防止一个集合有多个一样的数,再开一个vector,记录一下每次更新是不是 0,1的分界点即可。
Accepted code
#include<bits/stdc++.h>
#include<unordered_map>
using namespace std;
#define sc scanf
#define ls rt << 1
#define rs ls | 1
#define Min(x, y) x = min(x, y)
#define Max(x, y) x = max(x, y)
#define ALL(x) (x).begin(),(x).end()
#define SZ(x) ((int)(x).size())
#define pir pair <int, int>
#define MK(x, y) make_pair(x, y)
#define MEM(x, b) memset(x, b, sizeof(x))
#define MPY(x, b) memcpy(x, b, sizeof(x))
#define lowbit(x) ((x) & -(x))
#define P2(x) ((x) * (x))
typedef long long ll;
const int Mod = 1e9 + 7;
const int N = 1e5 + 100;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
inline ll dpow(ll a, ll b){ ll r = 1, t = a; while (b){ if (b & 1)r = (r*t) % Mod; b >>= 1; t = (t*t) % Mod; }return r; }
inline ll fpow(ll a, ll b){ ll r = 1, t = a; while (b){ if (b & 1)r = (r*t); b >>= 1; t = (t*t); }return r; }
vector <int> G[N], mx[N]; // 存图,大堆点集
int a[N], n, m;
int d[N], limit; // 度数,根号限制
bool vis[350];
void init() {
for (int i = 0; i <= n; i++)
G[i].clear(), mx[i].clear(), d[i] = 0;
}
// 二维树状数组
vector <int> bt[350];
vector <int> nb[350];
int sz[350], idx[N], cnt;
void Add(int x, int y, int w) {
int szz = sz[x];
while (y <= szz) {
bt[x][y] += w;
y += lowbit(y);
}
}
int Ask(int x, int y) {
int tot = 0;
while (y > 0) {
tot += bt[x][y];
y -= lowbit(y);
}
return tot;
}
// 更新大堆
void Calc(int x, int w) {
for (auto v : mx[x]) {
int id = idx[v];
if (a[x] <= d[v]) { // 删旧
nb[id][a[x]]--;
if (!nb[id][a[x]] && a[x])
Add(id, a[x], -1);
}
if (w <= d[v]) { // 加新
nb[id][w]++;
if (nb[id][w] == 1 && w)
Add(id, w, 1);
}
}
a[x] = w;
}
// 查询
int Ask_Big(int x) {
if (!nb[idx[x]][0]) // 0特判
return 0;
int l = 0, r = sz[idx[x]];
int mex = sz[idx[x]];
while (l <= r) {
int mid = (l + r) >> 1;
if (Ask(idx[x], mid) < mid)
mex = mid, r = mid - 1;
else
l = mid + 1;
}
return mex;
}
int Ask_Small(int x) { // 小根暴力
for (int i = 0; i <= d[x]; i++)
vis[i] = false;
for (auto v : G[x])
if (a[v] <= d[x])
vis[a[v]] = true;
for (int i = 0; i <= d[x]; i++) {
if (!vis[i])
return i;
}
}
int main()
{
int T; cin >> T;
while (T--) {
sc("%d %d", &n, &m);
init();
for (int i = 1; i <= n; i++)
sc("%d", &a[i]);
for (int i = 0; i < m; i++) {
int u, v;
sc("%d %d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
d[u]++, d[v]++;
}
// 分块
cnt = 0;
limit = sqrt(n);
for (int i = 1; i <= n; i++) {
if (d[i] < limit)
continue;
idx[i] = ++cnt; // 重新编号
sz[cnt] = d[i]; // 树状数组大小
bt[cnt].resize(d[i] + 5);
nb[cnt].resize(d[i] + 5);
for (int j = 0; j <= d[i]; j++)
bt[cnt][j] = nb[cnt][j] = 0; // 清空
for (auto v : G[i]) { // 该位置的数量
if (a[v] > d[i]) // 超过度数不加
continue;
nb[cnt][a[v]]++;
if (nb[cnt][a[v]] == 1 && a[v]) // 01分界点
Add(cnt, a[v], 1);
}
}
for (int i = 1; i <= n; i++) { // 保存大堆
for (auto v : G[i]) {
if (d[v] >= limit)
mx[i].push_back(v);
}
}
int q;
sc("%d", &q);
while (q--) {
int op, x, w;
sc("%d %d", &op, &x);
if (op == 1) {
sc("%d", &w);
Calc(x, w);
}
else {
if (d[x] >= limit)
printf("%d\n", Ask_Big(x));
else
printf("%d\n", Ask_Small(x));
}
}
}
return 0; // 改数组大小!!!用pair记得改宏定义!!!
}