HDU 6576:Finding a MEX(图分块、根号均摊 + 权值分块数组)

HDU 6576:Finding a MEX(图分块、根号均摊 + 权值分块数组)_第1张图片


题目大意:一张带点权的无向图,有两种操作:
1、对每个点修改它的点权
2、查询这个点的所有邻接点的权值集合U 的 mex,mex{U} 即 U中没有的最小权值。

整个题目的算法和复杂度受限于节点的度。
首先答案一定在 [0,d[x]],d[x] 是 x 的度。
对于度数较小的点,查询可以维护一个权值数组,暴力枚举邻接点,之后暴力枚举权值,找到答案,修改直接O(1)修改。
对于度数较大的点,可以维护数据结构来维护 mex,查询直接在维护的数据结构上查询,修改时出了修改自己的权值,还要修改对邻接点中度数较大的点的影响。

考虑以 n \sqrt{n} n 作为区分轻节点和重节点的度数大小边界,下面不妨设 n = 300 \sqrt{n} = 300 n =300
修改时,除了修改自己的权值,还要维护周围最多 300 300 300 个重节点的数据结构。
查询时,对轻节点,最多需要对300个节点暴力,然后查找答案,这部分的复杂度为 O ( 300 ∗ n ) O(300 * n) O(300n),对重节点,需要用数据结构来查询一次答案。

如果使用线段树、平衡树、set等查询和修改操作均为 l o g log log 的数据结构,最终复杂度会达到 n n log ⁡ n n\sqrt n\log n nn logn

观察发现,每次修改,最多需要进行 n \sqrt n n 次数据结构修改,而查询只需要一次。
用权值分块数组,可以做到 O ( 1 ) O(1) O(1) 修改, O ( n ) O(\sqrt n) O(n ) 查询,均衡之下,总复杂度为 O ( n n ) O(n \sqrt n) O(nn )


代码:

#include
using namespace std;
const int maxn = 5e5 + 10;
vector<int> g[maxn], big[maxn];
int t,n,m,a[maxn],q;
int sqr;
struct block {
	int n, siz;
	vector<int> b;
	vector<int> sum;
	void init(int tot) {
		b.resize(tot + 1);
		siz = 300;
		sum.resize(tot / siz + 1);
		n = tot;
	}
	void update(int val,int t) {
		if (val > n) return ;
		if (t == 1) {
			b[val] += t;	
			if (b[val] == 1) 
				sum[val / siz]++;
		} else {
			b[val] += t;
			if (b[val] == 0)
				sum[val / siz]--;
		}
	}
	int qry() {
		for (int j = 0; j <= n / siz; j++) {
			if (sum[j] < siz) {
				int ed = min(n,(j + 1) * siz - 1);
				for (int k = j * siz; k <= ed; k++)
					if (!b[k]) return k;
			}
		}
	}
	void clear() {
		for (int i = 0; i <= n; i++)
			b[i] = 0;
		for (int i = 0; i <= n / siz; i++)
			sum[i] = 0;
	}
	void free() {
		sum.resize(0); 
		b.resize(0);
		n = siz = 0;
	}
}B[maxn]; 
int main() {
	scanf("%d",&t);
	while (t--) {
		scanf("%d%d",&n,&m);
		sqr = 300;
		for (int i = 1; i <= n; i++)
			scanf("%d",&a[i]);
		for (int i = 1, x, y; i <= m; i++) {
			scanf("%d%d",&x,&y);
			g[x].push_back(y);
			g[y].push_back(x);
		}
		for (int i = 1; i <= n; i++) {
			B[i].init(g[i].size());
		}
		for (int i = 1; i <= n; i++) {
			for (auto it : g[i]) {
				B[i].update(a[it],1);
				if (g[it].size() > sqr)
					big[i].push_back(it);
			}
		}
		scanf("%d",&q);
		while (q--) {
			int c, x, y;
			scanf("%d%d",&c,&x);
			if (c == 1) {
				scanf("%d",&y);
				for (auto v : big[x])
					B[v].update(a[x],-1);
				a[x] = y;
				for (auto v : big[x])
					B[v].update(a[x],1);
			} else {
				if (g[x].size() > sqr)
					printf("%d\n",B[x].qry());
				else {
					B[x].clear();
					for (auto v : g[x])
						B[x].update(a[v],1);
					printf("%d\n",B[x].qry());	
				}
			}
		}		
		for (int i = 1; i <= n; i++) {
			B[i].free();
			g[i].clear();
			big[i].clear();
		}
	}
	return 0;
}

你可能感兴趣的:(分块,根号均摊)