NOI.AC#2007-light【根号分治】

正题

题目链接:http://noi.ac/problem/2007


题目大意

\(n\)个格子排成一排,每个格子有一个\(0/1\)和一个颜色。开始每个格子都是\(0\)\(q\)次操作取反一个颜色的所有格子的\(0/1\),然后询问\(1\)的格子构成的连通块数量。

\(1\leq n,q\leq 10^5\)


解题思路

可以理解为总共的\(1\)格子数减去相邻的\(1\)格子对数。

转换一下模型,每队相邻的颜色\(x,y\)之间连接一条边。

现在问题变为了每次删除或者加入一个点,求连通子图的边的数量。

那么每次加入一个点\(x\)的时间复杂度是\(O(deg_x)\),这其实是有大量重复的,因为有一些点没有被加入但是也需要判断。

考虑平衡一下复杂度,发现对于一条边连接\(x,y\),我们可以选择一个点在这个点修改的时候进行处理,若两个点的度数都在\(\sqrt m\)以内那么随便那个点处理这条边的情况就好了。若其中有一个点的度数大于\(\sqrt m\)的话,那么度数小的那个点处理。

然后两个点的度数都大于\(\sqrt m\)的话怎么办?不难发现这些点的数量不会超过\(\sqrt m\),我们将重边压缩然后随便那个点处理都可以。

这样均摊下来时间复杂度\(O(q\sqrt n)\)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<cmath>
using namespace std;
const int N=1e5+10;
int l,m,n,Q,w[N],c[N],deg[N],ans;
bool v[N],z[N];vector<int>e[N]; 
int main()
{
	scanf("%d%d%d",&l,&n,&Q);
	for(int i=1;i<=l;i++){
		scanf("%d",&c[i]);
		w[c[i]]+=1-(c[i]==c[i-1]);
		if(c[i]!=c[i-1])deg[c[i]]++,deg[c[i-1]]++,m+=2;
	}
	int T=sqrt(m);
	for(int i=1;i<=n;i++)z[i]=(deg[i]>T);
	for(int i=2;i<=l;i++){
		if(c[i]!=c[i-1]){
			int x=c[i],y=c[i-1];
			if(!z[x])e[x].push_back(y);
			else e[y].push_back(x);
		}
	}
	while(Q--){
		int x;scanf("%d",&x);
		int f=v[x]?-1:1;v[x]^=1;ans+=w[x]*f;
		for(int i=0;i<e[x].size();i++){
			int y=e[x][i];w[y]-=f;
			if(v[y])ans-=f;
		}
		printf("%d\n",ans);
	}
	return 0;
}
posted @ 2021-04-01 11:45  QuantAsk  阅读(82)  评论(0编辑  收藏  举报