前言
我爱任何形式的暴力
这道题解法是我比较薄弱但喜欢的分块
这是我写这篇博客的原因
而且这道题让我心态崩掉,直接重构代码,总用时2h
理清思路并不难写(第一次尝试写代码之前写一份类似于大纲的东西
感觉不错,下次复杂的题也这么做
题目
题目大意:
多组输入
给定 (n) 个点 (m) 条边的一个无向图,每个点 (i) 有个权值 (a_i)
有 (q) 个操作
操作(1)为将第 (u) 号点的权值改为 (x)
操作(2)为询问与 (u) 相邻(不包含自己)的点的权值的(MEX)
(MEX)为最小的未出现过的自然数
讲解
为我们表述方便,我们将度数小于(sqrt{n})的点叫做小点,其余叫做大点
前言已经提到过,这道题分块
查询
对于小点,我们直接暴力查找即可
对于大点,我们考虑将权值分块,对于每个块我们维护有多少元素确实,对于每个大点维护每个元素出现了多少次
显然大点的个数很少,不超过 (sqrt{n}) 级别
我们只需从小到大访问询问的大点的块,找出有缺失的块,然后在其中暴力查找即可
其中有一个优化,因为 (MEX) 只可能达到度数级别,所以点的权值如果超过了度数,我们可将其赋为度数-1
修改
修改就不分大点、小点了,对于每个要修改的点,我们要更新它所在的每个块的信息
一个点最多能被多少个大点包含呢?显然也是 (sqrt{n}) 级别,暴力修改即可
代码
许久未见的完整版代码
//12252024832524
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int MAXN = 100005;
const int MAXB = 318;
int n,m,ddd;
int a[MAXN];
int Read()
{
int x = 0,f = 1;char c = getchar();
while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
void Put1(int x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
void Put(int x,char c = -1)
{
if(x < 0) putchar('-'),x = -x;
Put1(x);
if(c >= 0) putchar(c);
}
template <typename T>T Max(T x,T y){return x > y ? x : y;}
template <typename T>T Min(T x,T y){return x < y ? x : y;}
template <typename T>T Abs(T x){return x < 0 ? -x : x;}
int head[MAXN],tot,d[MAXN];//d:度数
struct edge
{
int v,nxt;
}e[MAXN << 1];
void Add_Edge(int x,int y)
{
e[++tot].v = y;
e[tot].nxt = head[x];
head[x] = tot;
}
void Add_Double_Edge(int x,int y)
{
Add_Edge(x,y); d[y]++;
Add_Edge(y,x); d[x]++;
}
int ID[MAXN],rID[MAXN],siz[MAXN],btot;//ID,rID:点号与大点编号的互相映射,siz:大点分块大小
int cnt[MAXB][MAXN],cha[MAXB][MAXB];//cnt:每个大点的元素计数器,cha:每个大点的每个块差多少元素填满
int cfa[MAXN];//cnt for ans(奇怪的英语增加了
vector<int> bl[MAXN];//每个点被哪些大点包含
int main()
{
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);
for(int T = Read(); T ;-- T)
{
for(int i = n;i >= 1;-- i)
if(d[i] >= ddd)
{
for(int j = head[i]; j ;j = e[j].nxt)
cnt[btot][Min(a[e[j].v],d[i]-1)] = 0;
btot--;
}
//初始化确实烦人
n = Read(); m = Read();
btot = tot = 0;
for(int i = 1;i <= n;++ i) ID[i] = head[i] = d[i] = 0,bl[i].clear();
memset(cha,0,sizeof(cha));
for(int i = 1;i <= n;++ i) a[i] = Read();
for(int i = 1;i <= m;++ i) Add_Double_Edge(Read(),Read());
ddd = ceil(sqrt(n));
for(int i = 1;i <= n;++ i)
if(d[i] >= ddd)//大大大
{
ID[i] = ++btot;
rID[btot] = i;
siz[i] = ceil(sqrt(d[i]));//每个大点分为sqrt(d[i])个块
for(int j = 0;j <= siz[i];++ j) cha[btot][j] = siz[i];
for(int j = head[i]; j ;j = e[j].nxt)
{
int val = Min(a[e[j].v],d[i]-1);
bl[e[j].v].push_back(btot);//e[j].v被大点btot包含
cnt[btot][val]++;
if(cnt[btot][val] == 1) cha[btot][val / siz[i]]--;
}
}
for(int Q = Read(); Q ;-- Q)
{
int opt = Read(),x = Read();
if(opt == 1)
{
int val = Read();
for(int j = 0,len = bl[x].size();j < len;++ j)
{
//删除以前的信息
int bid = bl[x][j],jval = Min(a[x],d[rID[bid]]-1);
cnt[bid][jval]--;
if(!cnt[bid][jval]) cha[bid][jval / siz[rID[bid]]]++;
//更新现在的信息
jval = Min(val,d[rID[bid]]-1);
cnt[bid][jval]++;
if(cnt[bid][jval] == 1) cha[bid][jval / siz[rID[bid]]]--;
}
a[x] = val;
}
else
{
int ans = n;
if(d[x] < ddd)//小点暴力找
{
for(int i = head[x]; i ;i = e[i].nxt) cfa[Min(n,a[e[i].v])]++;
for(int i = 0;i <= n && ans == n;++ i)
if(!cfa[i]) ans = i;
for(int i = head[x]; i ;i = e[i].nxt) cfa[Min(n,a[e[i].v])]--;
}
else//大点分块
{
int bid = ID[x];
for(int i = 0;i <= siz[x] && ans == n;++ i)//siz[x]个块
if(cha[bid][i])//该块有空缺
for(int j = i * siz[x];j < (i+1) * siz[x] && ans == n;++ j)//每个块有siz[x]个元素
if(!cnt[bid][j])
ans = j;
}
Put(ans,'
');
}
}
}
return 0;
}