2020杭电HDU-6756多校第一场Finding a MEX(图的分块)

Posted lonely-wind-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2020杭电HDU-6756多校第一场Finding a MEX(图的分块)相关的知识,希望对你有一定的参考价值。

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6756
CSDN食用链接:https://blog.csdn.net/qq_43906000/article/details/107590312

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≤10^9).
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≤10^ 5), m (1≤m≤10^5) 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≤10^9).

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≤10^5) 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

题目大意:给你一个n个点,m条边的图,每个点有点权,定义(f(u)) 表示与点(u)相连的点不存在的最小权值,现在有两种操作:
1.将点(u)的值改变为(x)
2.查询(F(u))

emmm,刚开始读错了题目,以为是将所有为(A_u)的值都改变为(x),然后怎么想都觉得搞不定QAQ。。

事实上分块的题目我倒是做过不少,但这种图的分块还真没怎么接触过QAQ。。。

emmm,先建图吧,然后我们定义大点为度数大于(sqrt{n})的点,然后对于每个点我们建立一个邻居值集,并对这个集合进行分块处理,如下所示:

void insert(int u,int val)
{
	if (val>du[u]) val=du[u]+1;//u的分支就du[u]个,如果邻居的值大于du[u]
	//说明我们一定可以在du[u]以内个数中找到一个不存在的最小值。
	//所以对于这样的邻居值是没有意义的,稍微记录一下就好了
	cnt[u][val]++;
	if (cnt[u][val]==1) tag[u][val/block_size]++;//值域分块用于快速查找不存在的值
}

修改的时候,对于小的点,我们直接暴力修改就好了,也就是遍历它的所有邻居,然后从该邻居的值集中删掉该点的旧值,并加入该点的新值,如下所示:

if (tp==1) {
	scanf ("%d",&x);
	if (du[u]>block_size) a[u]=x;
	else {
		for (int i=head[u]; i!=-1; i=eg[i].next) {
			int v=eg[i].to;
			delet(v,a[u]);//u被改变之后,它的邻居的值集也会变化
			insert(v,x);
		}
		a[u]=x;
	}
}

可以看到,对于大点,我们没有暴力修改,因为它所需要的时间会非常久。我们只是将(A_u)进行了修改,那么我们如何查询与(u)相邻的点的(MEX)值呢?所以我们再引入一个东西来保存每个大点的旧值,而这个旧值也就只会在询问的时候发生改变。我们记录每个点的邻居是大点的情况,并将其点和点权放入,询问与大点相邻的点的(MEX)值的时候我们就可以只更改该点的邻居值集,如下所示:

for (int i=1; i<=n; i++) {
	for (int j=head[i]; j!=-1; j=eg[j].next) {
		int v=eg[j].to;
		if (du[v]>block_size)
			big_ngb[i].push_back(make_pair(v,a[v]));//邻居是大点的情况
		insert(i,a[v]);//记录邻居值的出现次数
	}
}
for (int i=0; i<big_ngb[u].size(); i++) {
	//遍历邻居中的大点,因为大点还没有被改变,所以我们要将他改变
	int v=big_ngb[u][i].first;
	int pre_a=big_ngb[u][i].second;//大点的旧值
	if (pre_a!=a[v]) {
		delet(u,pre_a);
		insert(u,a[v]);
		big_ngb[u][i].second=a[v];
	}
}
printf("%d
",query(u));

那么query也就很好写了,就是个普通的分块,每次查看块是否被填满了,如果没有的话就说明答案一定在里面。

以下是AC代码:(值得一提的是,我加了读入挂和输出挂跑的居然比scanf还慢QAQ,但基本都在1400ms+)

#include <bits/stdc++.h>
using namespace std;

const int mac=1e5+10;
const int inf=1e9+10;

struct Edge
{
	int to,next;
}eg[mac<<1];
int head[mac],num=0;
int a[mac],block_size=0,block_num=0;
int du[mac];
vector<int>tag[mac],cnt[mac];
vector<pair<int,int>>big_ngb[mac];//每个点的大点邻居

void add(int u,int v)
{
	eg[++num]=Edge{v,head[u]};
	head[u]=num;
}

void insert(int u,int val)
{
	if (val>du[u]) val=du[u]+1;//u的分支就du[u]个,如果邻居的值大于du[u]
	//说明我们一定可以在du[u]以内个数中找到一个不存在的最小值。
	//所以对于这样的邻居值是没有意义的,稍微记录一下就好了
	cnt[u][val]++;
	if (cnt[u][val]==1) tag[u][val/block_size]++;//值域分块用于快速查找不存在的值
}

void delet(int u,int val)
{
	if (val>du[u]) val=du[u]+1;
	cnt[u][val]--;
	if (cnt[u][val]==0) tag[u][val/block_size]--;
}

int query(int u)
{
	for (int i=0; i<=du[u]; i++){//总共du[u]+1个值,而有du[u]个邻居,那么答案一定在其中
		int gkd=tag[u][i/block_size];
		if (gkd==block_size) {i+=block_size-1;  continue;}//这个块被填满了,说明答案不在其中
		for (int j=i; j<i+block_size; j++){
			if (!cnt[u][j]) 
				return j;
		}
	}
}

void init(int n)
{
	memset(head,-1,sizeof head);
	num=0;
	memset(du,0,sizeof du);
	for (int i=0; i<=n; i++){
		big_ngb[i].clear();
		cnt[i].clear();  tag[i].clear();
	}
}

int main(int argc, char const *argv[])
{
	int t;
	scanf ("%d",&t);
	while (t--){
		int n,m;
		scanf ("%d%d",&n,&m);
		block_size=sqrt(n);
		init(n);
		for (int i=1; i<=n; i++)
			scanf ("%d",&a[i]);
		for (int i=1; i<=m; i++){
			int u,v;
			scanf ("%d%d",&u,&v);
			add(u,v);add(v,u);
			du[u]++,du[v]++;
		}
		for (int i=1; i<=n; i++){
			cnt[i].resize(du[i]+2);
			tag[i].resize(du[i]/block_size+2);//块的个数
		}
		for (int i=1; i<=n; i++){
			for (int j=head[i]; j!=-1; j=eg[j].next){
				int v=eg[j].to;
				if (du[v]>block_size) 
					big_ngb[i].push_back(make_pair(v,a[v]));//邻居是大点的情况
				insert(i,a[v]);//记录邻居值的出现次数
			}
		}
		int q;
		scanf ("%d",&q);
		while (q--){
			int tp,u,x;
			scanf ("%d%d",&tp,&u);
			if (tp==1){
				scanf ("%d",&x);
				if (du[u]>block_size) a[u]=x;
				else {
					for (int i=head[u]; i!=-1; i=eg[i].next){
						int v=eg[i].to;
						delet(v,a[u]);//u被改变之后,它的邻居的值集也会变化
						insert(v,x);
					}
					a[u]=x;
				}
			}
			else {
				for (int i=0; i<big_ngb[u].size(); i++){
					//遍历邻居中的大点,因为大点还没有被改变,所以我们要将他改变
					int v=big_ngb[u][i].first;
					int pre_a=big_ngb[u][i].second;//大点的旧值
					if (pre_a!=a[v]){
						delet(u,pre_a);
						insert(u,a[v]);
						big_ngb[u][i].second=a[v];
					}
				}
				printf("%d
",query(u));
			}
		}
	}
	return 0;
}






















以上是关于2020杭电HDU-6756多校第一场Finding a MEX(图的分块)的主要内容,如果未能解决你的问题,请参考以下文章

2019.07.222019杭电多校第一场

2019 杭电多校第一场

2018杭电多校第一场(A)

2019杭电多校第一场

2022杭电多校第一场01

2022杭电多校第一场01