灯泡

灯泡

题目描述

\(n\) 个灯泡,一开始所有灯泡都是灭着的。一共有 \(k\) 种颜色,其中第 \(i\) 个灯泡的颜色为 \(c_i\)。有 \(q\) 次操作。每次操作给出一个正整数 \(x\),表示将所有颜色为 \(x\) 的灯泡状态反转(开着就关上,关着就打开)。每次操作完都需要输出极长亮灯段的数量。形式化地,输出符合以下条件的 \([l,r]\) 的个数:

  1. \(1\le l\le r\le n\)
  2. 编号在 \([l,r]\) 之间的灯全部处于打开状态,而编号为 \(l-1\)\(r+1\) 的灯都关着。

数据范围

\(1\le n,q\le2\times10^5\)\(1\le k\le n\)\(1\le c_i,x\le k\)

时空限制

\(1\text{s}\)\(512\text{Mb}\)

做法

谨以此博客纪念我写出的第一道根号分治题。

首先,我们可以把一段连续的、颜色相同的灯缩成一段,这是显然正确的。所以下文我们所讨论都是缩完之后的序列。然后,我们考虑一个灯泡状态发生变化时对答案造成的影响。容易发现,答案的变化量只和自己以及自己两边的灯泡有关系。假设这个灯泡两边有 \(a\) 盏灯开着,\(b\) 盏灯关着(\(a+b=2\)),注意如果有一边没有灯泡那么就算有一个关着的灯泡。那么如果是 \(1\to0\),那么答案会加上 \(b-1\);如果是 \(0\to1\),那么答案会加上 \(a-1\)

接下来考虑根号分治。有了上述结论,难点在于对于一种颜色如何去计算 \(a\) 的和以及 \(b\) 的和。将颜色分成两类。第一类是灯泡数量大于 \(\sqrt n\) 的颜色,容易发现这样的颜色种类数不会超过 \(\sqrt n\);第二类是剩下的颜色。于是,我们先预处理出每种颜色的灯泡和每种第一类颜色的灯泡一共有多少对相邻的,反转某种颜色时直接修改每种第一类颜色的 \(a\) 和和 \(b\) 和。对于第二类颜色不用管它,等到修改到它的时候直接暴力统计它的 \(a\) 和和 \(b\) 和即可,因为这种颜色的灯泡数量不超过 \(\sqrt n\)。这样就做完了,时间复杂度 \(O(q\sqrt n)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,q,k;
const int N=2e5+10,B=500;
int n0[N],n1[N],bit[N];
int c[N],a[N];
int cnt,coun[N];
vector<int> big,co[N];
int num[N][410];
int getid(int x){
	return lower_bound(big.begin(),big.end(),x)-big.begin();
}
int ans;
int main(){
//	freopen("bulb.in","r",stdin);
//	freopen("bulb.out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>q>>k;
	for(int i=1;i<=n;i++) cin>>c[i];
	for(int i=1;i<=n;i++)
		if(c[i]!=c[i-1]) a[++cnt]=c[i],coun[c[i]]++;
	for(int i=1;i<=cnt;i++) co[a[i]].emplace_back(i);
	for(int i=1;i<=k;i++) if(coun[i]>=B) big.emplace_back(i);
	for(int i=0;i<big.size();i++){
		for(auto j:co[big[i]]){
			if(j>1) num[a[j-1]][i]++;
			if(j<cnt) num[a[j+1]][i]++;
			n0[big[i]]+=2;
		}
	}
	for(int i=1;i<=q;i++){
		int x;
		cin>>x;
		if(coun[x]>=B){
			if(bit[x]) ans-=n0[x]-co[x].size();
			else ans-=n1[x]-co[x].size();
		}
		else{
			int num0=0,num1=0;
			for(auto j:co[x]) num0+=(!bit[a[j-1]])+(!bit[a[j+1]]),num1+=bit[a[j-1]]+bit[a[j+1]];
			if(bit[x]) ans-=num0-co[x].size();
			else ans-=num1-co[x].size();
		}
		cout<<ans<<'\n';
		for(int j=0;j<big.size();j++){
			if(!bit[x]) n0[big[j]]-=num[x][j],n1[big[j]]+=num[x][j];
			else n1[big[j]]-=num[x][j],n0[big[j]]+=num[x][j];
		}
		bit[x]^=1;
	}
}
posted @ 2025-07-18 20:49  Fiendish  阅读(14)  评论(0)    收藏  举报