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(图的分块)的主要内容,如果未能解决你的问题,请参考以下文章