模拟赛2021.4.4 灯(light)——分块思想

题目描述

题解

首先答案应该是线段树,但是对于每次更改非连续的几段难以用线段树,所以把答案转换为:亮着的灯数量-相邻两灯都亮的对数,前者预处理,后者可以用一个分块。

把灯数量超过BL的颜色称为大色,其余称为小色,那么对于每种颜色,记f[i]为与第 i 种颜色的灯相邻的、亮着的、颜色为小色的灯的数量,那么维护它只需要每次修改小色对所有相邻颜色做贡献(O(BL)),然后对于当前修改的颜色 i,枚举每一种亮着的大色 j (O(\frac{n}{BL})),算上 i 与 j 相邻的灯的对数(对于 i 是小色,直接枚举相邻颜色,对于 i 是大色,可以预处理与其他大色相邻的灯的对数)的贡献。当BL=\sqrt{n}时,总复杂度O(n\sqrt{n})

考场上为了节约时间,直接码数据结构,还做了一些后来发现很蠢的多余操作,然后因常数大而T。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#define ll long long
#define uns unsigned
#define MAXN 100005
#define INF 0x3f3f3f3f
using namespace std;
inline ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+s-'0',s=getchar();
	return f?x:-x;
}
int n,m,q,c[MAXN],N,num[MAXN];
int id[MAXN],d[320],IN,ans;
int ln[320][320],nl[MAXN];
vector<int>p[MAXN];
bool vis[MAXN];
signed main()
{
//	freopen("light.in","r",stdin);
//	freopen("light.out","w",stdout);
	n=read(),m=read(),q=read(),N=310;
	for(int i=1;i<=n;i++)c[i]=read(),num[c[i]]++;
	for(int i=1;i<=m;i++)if(num[i]>N)id[i]=++IN,d[IN]=i;
	for(int i=2;i<=n;i++){
		if(c[i]==c[i-1])num[c[i]]--;
		else{
			p[c[i]].push_back(i-1),p[c[i-1]].push_back(i);
			if(id[c[i]]>0&&id[c[i-1]]>0)ln[id[c[i-1]]][id[c[i]]]++;
		}
	}
	while(q--){
		int a=read();
		
		if(vis[a])ans-=num[a],ans+=nl[a];
		else ans+=num[a],ans-=nl[a];
		if(id[a]>0){
			for(int i=1;i<=IN;i++)
				if(vis[d[i]]){
					if(vis[a])ans+=ln[i][id[a]]+ln[id[a]][i];
					else ans-=ln[i][id[a]]+ln[id[a]][i];
				}
		}else{
			for(uns i=0;i<p[a].size();i++)
				if(id[c[p[a][i]]]>0&&vis[c[p[a][i]]]){
					if(vis[a])ans++;
					else ans--;
				}
		}
		
		if(id[a]==0){
			for(uns i=0;i<p[a].size();i++){
				if(vis[a])nl[c[p[a][i]]]--;
				else nl[c[p[a][i]]]++;
			}
		}
		vis[a]^=1;
		
		printf("%d\n",ans);
	}
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值