[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\) ¶
分块,没什么好说的。
但是分块维护的东西,不能针对一种方法暴力维护,因为某些情况可能很难维护,有时候从另外的方法考虑如何计算答案,可能会得到更好维护的东西。