[20210404]ZXY的卡常故事

壹、题面 ¶

§ 1.1.题目描述 §

在一个夜黑风高的夜晚,\(\sf ZXY\) 想起来祂常常经历的一件事情——卡大常。

作为一个写代码有着大常数的妹妹,祂用尽各种方法,终于将祂的 \(n\) 道题卡过,但是,数据随时在更新,某些时候,祂用同一种方法做的题都会被最新的数据卡掉,这让祂不得不又去卡常。

简要地说,\(\sf ZXY\) 曾经做过 \(n\) 道题,这 \(n\) 道题一共使用了 \(m\) 种方法,最初 \(\sf ZXY\) 的所有题都是过了的,现在有 \(q\) 个事件,对于每个事件,给定一个 \(k\),分两种情况:

  • 如果使用第 \(k\) 种方法的题之前被卡掉了,那么这个事件表示 \(\sf ZXY\) 减小了第 \(k\) 种方法的常数,所有使用第 \(k\) 种方法的题都可以过了;
  • 如果使用第 \(k\) 种方法的题在之前是通过状态,那么这个事件表示 \(\sf ZXY\)所有使用这种方法的题目都被最新数据卡掉,无法通过了;

由于 \(\sf ZXY\) 喜欢连续,祂会给你一个序列 \(a_i\),序列第 \(i\) 个数表示第 \(i\) 道题使用的是方法 \(a_i\),每次事件之后,祂会询问你最多少极长的连续通过的题的连续段。

由于祂是神,所以如果你无法使用最优方法,祂会愤怒于你没有庆祝祂卡大常而赐予你超级无敌大常数,让你无法过掉除 \(\tt subtask\#1\) 以外的所有数据点。

§ 1.2.样例 §

样例输入

3 2 5
1 2 1
1
2
1
2
2

样例输出

2
1
1
0
1

§ 1.3.数据范围 §

n m q
\(\tt subtask\#1,13pts\) \(\le 5000\) \(\le n\) \(\le 5000\)
\(\tt subtask\#2,35pts\) \(\le 10^5\) \(\le 100\) \(\le 10^5\)
\(\tt subtask\#3,52pts\) \(\le 10^5\) \(\le n\) \(\le 10^5\)

对于 \(100\%\) 的数据,满足 \(1\le a_i\le m\).

贰、题解 ¶

对于 \(n\le 10^5\) 的范围,不难想到 \(\mathcal O(n\log^2)\) 或者 \(\mathcal O(n\sqrt n)\) 的算法,如果是前者,则是数据结构方向,这方面没有教好的、处理极长连续段的方法,那么考虑后者,分块方向。

不难发现,对于每个方法的出现次数进行分块。

考试的时候使用以下特性:

将一个方法卡过,那么加上的答案就是 位置左右都是暗的的个数,而答案减去的是 位置左右都是亮的位置数,卡掉一个方法同理。

那么我们就需要维护方法和它左右的情况,这样很不好做......然后我考试的时候就吊死了......

实际上,我们得使用另外一个特点,连续段数等于通过的题数减相邻两个都通过的题对数。

\(k=\sqrt n\),对于出现次数 \(\ge k\) 的方法,我们维护与这种方法相邻的通过的题对数。每次我们改变 \(\ge k\) 的方法时,直接用这个算答案。改变 \(\le k\) 的方法就暴力枚举每个位置。

时间复杂度 \(\mathcal O(n\sqrt n)\).

叁、参考代码 ¶

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;

template<class T>inline T readin(T x){
	x=0; int f=0; char c;
	while((c=getchar())<'0' || '9'<c) if(c=='-') f=1;
	for(x=(c^48); '0'<=(c=getchar()) && c<='9'; x=(x<<1)+(x<<3)+(c^48));
	return f? -x: x;
}

const int block=320;
const int maxn=1e5;

int n, m, q;
int c[maxn+5];
int amo[maxn+5];
vector<int>pos[maxn+5];

inline void input(){
	n=readin(1), m=readin(1), q=readin(1);
	for(int i=1; i<=n; ++i){
		c[i]=readin(1); ++amo[c[i]];
		pos[c[i]].push_back(i);
	}
}

int bigid[maxn+5], cid;
int ref[block+5];
inline void getbig(){
	// scan the colors
	for(int i=1; i<=n; ++i) if(amo[i]>=block){
		bigid[i]=++cid;
		ref[cid]=i;
	}
}

int selfby[maxn+5];
int conf[block+5][block+5];
inline void getby(){
	// scan the posi
	for(int i=1; i<=n; ++i){
		if(c[i]==c[i+1]) ++selfby[c[i]];
		else if(bigid[c[i]] && bigid[c[i+1]]){
			++conf[bigid[c[i]]][bigid[c[i+1]]];
			++conf[bigid[c[i+1]]][bigid[c[i]]];
		}
	}
}

int smallby[block+5];
int status[maxn+5];
inline void getquery(){
	int x;
	int on=0, nearby=0;
	while(q--){
		x=readin(1);
		// printf("bigid[%d] == %d, selfby == %d\n", x, bigid[x], selfby[x]);
		// a big color
		if(bigid[x]){
			// printf("smallby == %d\n", smallby[bigid[x]]);
			if(status[x]){
				on-=pos[x].size();
				nearby-=selfby[x];
				nearby-=smallby[bigid[x]];
				for(int i=1; i<=cid; ++i) if(i!=bigid[x] && status[ref[i]])
					nearby-=conf[bigid[x]][i];
			}
			else{
				on+=pos[x].size();
				nearby+=selfby[x];
				nearby+=smallby[bigid[x]];
				for(int i=1; i<=cid; ++i) if(i!=bigid[x] && status[ref[i]])
					nearby+=conf[bigid[x]][i];
			}
		}
		else{
			if(status[x]){
				on-=pos[x].size();
				nearby-=selfby[x];
				for(int i=0, p, siz=pos[x].size(); i<siz; ++i){
					p=pos[x][i];
					if(c[p]!=c[p-1]) nearby-=status[c[p-1]];
					if(c[p]!=c[p+1]) nearby-=status[c[p+1]];
					if(bigid[c[p-1]]) --smallby[bigid[c[p-1]]];
					if(bigid[c[p+1]]) --smallby[bigid[c[p+1]]];
				}
			}
			else{
				on+=pos[x].size();
				nearby+=selfby[x];
				for(int i=0, p, siz=pos[x].size(); i<siz; ++i){
					p=pos[x][i];
					// printf("When p == %d\n", p);
					if(c[p]!=c[p-1]) nearby+=status[c[p-1]];
					if(c[p]!=c[p+1]) nearby+=status[c[p+1]];
					// printf("nearby == %d\n", nearby);
					if(bigid[c[p-1]]) ++smallby[bigid[c[p-1]]];
					if(bigid[c[p+1]]) ++smallby[bigid[c[p+1]]];
				}
			}
		}
		status[x]^=1;
		// printf("on == %d, nearby == %d\n", on, nearby);
		printf("%d\n", on-nearby);
	}
}

signed main(){
	freopen("light.in", "r", stdin);
	freopen("light.out", "w", stdout);
	input();
	getbig();
	getby();
	getquery();
	return 0;
}

肆、用到 の \(\tt trick\)

分块,没什么好说的。

但是分块维护的东西,不能针对一种方法暴力维护,因为某些情况可能很难维护,有时候从另外的方法考虑如何计算答案,可能会得到更好维护的东西。

posted @ 2021-04-04 17:32  Arextre  阅读(86)  评论(0编辑  收藏  举报