2020杭电多校第一场 HDU6756 Finding a MEX (分块&树状数组)

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.
 

 

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.
 

 

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
#include
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 
#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  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  bt[350];
vector  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记得改宏定义!!!
}

 

你可能感兴趣的:(图论,线段树)