题目
题目背景
校长使用「技能:发链接」,
O
I
D
\sf OID
OID 触发「事件:摆烂」。
「事件:摆烂」
【效果】:专注力 -100 \text{-100} -100,你立刻进入脱离学习状态。
校长使用「技能:扣帽子」, O I D \sf OID OID 获得「 d e b u f f \rm debuff debuff:美国之名」。
「技能:扣帽子」
【描述】:“他说别人是什么,别人就是什么。他是真正的美国。” “你刚刚说什么?我没听清。” “我说他是真正的美国。” “好,他是真正的美国。”
【效果】:被该技能命中的角色,获得「 d e b u f f \rm debuff debuff:美国之名」。
「 d e b u f f \rm debuff debuff:美国之名」
【描述】:今后你最好躲着校长走。这样至少能够保住你的命,暂时地。
【效果】:专注力 -30 \text{-30} -30,士气 -10 \text{-10} -10 。失去「阵营:校领导」的援助。
卷爷使用「技能:禁止朝圣」, O I D \sf OID OID 获得「 d e b u f f \rm debuff debuff:神的弃子」。
「 d e b u f f \rm debuff debuff:神的弃子」
【描述】:“我的忍耐也是有限度的。” 你记得神的每句话,哪怕是你能听到的最后一句。你本可以继续负重前行,如果神不曾弃你而去。
【效果】:士气 -50 \text{-50} -50,情绪 -100 \text{-100} -100 。失去「阵营:神」的援助。
校长触发「事件:切 T 2 T2 T2」。卷爷触发「事件:切 T 2 T2 T2」。
「事件:切 T 2 T2 T2」
【效果】:未触发该事件的所有角色,士气 -30 \text{-30} -30 。
【属性】:该事件只在具有「特征:强者」的角色上发生。
召唤巨大蛋狗!角色:巨大蛋狗登场!巨大蛋狗触发「事件:切 T 2 T2 T2」。
巨大蛋狗使用「技能:吃人」, O I D \sf OID OID 失去战斗能力!
「技能:吃人」
【描述】:如果你的体型足够小,有几率不被牙齿撕碎。不过,多活十几秒似乎并没有什么意义。
【效果】:被该技能命中的角色,健康度 -100 \text{-100} -100,立刻失去战斗能力。
校长达成「目标:强者渴望孤独,孤独渴望我」。
卷爷达成「目标:神欲使其灭亡,必先使其疯狂」。
巨大蛋狗达成「目标:人类一败涂地」。
O I D \sf OID OID 未达成「目标:苟活」。戰鬥終了!
完整题面
传送门 to luogu
思路
看到 ∑ n ⩽ 5 × 1 0 5 \sum n\leqslant 5\times 10^5 ∑n⩽5×105,感觉必定 O ( n log n ) \mathcal O(n\log n) O(nlogn) 。然后我成了 🤡。
若最终众数为 c c c,答案为 ( ( ( 数字 c c c 总数 ) ) ) + + + ( ( ( 区间众数的出现次数 ) ) ) − - − ( ( ( 区间内 c c c 出现次数 ) ) ) 。
感觉 c c c 必须枚举1,然后至少需要扫描整个序列吧。那么可枚举的 c c c 不太多啊。枚举出现次数比较多的 c c c,则更接近于 “整个序列”,比较划算。哈哈,出现次数超过 n \sqrt{n} n 的可以枚举!
区间众数怎么算?当然还是枚举,别忘了我们只有 O ( n ) \mathcal O(\sqrt{n}) O(n) 个 c c c 要检查!枚举之后,问题变为最大子段和——若为假定的区间众数则 + 1 +1 +1,若为 c c c 则 − 1 -1 −1 。容易注意到2 最大子段的端点必须为 + 1 +1 +1,所以只需要处理 + 1 +1 +1 的位置,而无需扫描整个序列。因此,对于某个 c c c,总复杂度为 O ( n ) \mathcal O(n) O(n) 。
接下来,是出现次数很少的 c c c 。区间众数仍考虑枚举的话,感觉只能枚举出现次数超过 n \sqrt{n} n 的。受上面的启发,我们尽量在 O [ min ( \mathcal O[\min( O[min(出现次数 ) ] )] )] 内求解。令最大子段的端点在 − 1 -1 −1 旁即可。时间复杂度 O ( n n ) \mathcal O(n\sqrt{n}) O(nn) 。
然后就不能枚举了。我们只好通盘考虑。可以 O ( \mathcal O( O( c c c 的出现次数 ) ) ) 枚举最大子段的某个端点,比如右端点。我们需要维护左端点 ℓ \ell ℓ 对应的答案。显然众数是比较难搞的。但是众数的出现次数 ⩽ n \leqslant\sqrt{n} ⩽n,所以等值段只有 O ( n ) \mathcal O(\sqrt{n}) O(n) 个,每个等值段的最优 ℓ \ell ℓ 就是等值段右端点。维护等值段,感觉可行。
update \text{update} update:维护每个段,使用 vector \texttt{vector} vector 等,常数巨大!所以可以暴力存储 ν ℓ \nu_\ell νℓ 表示众数出现次数,更新则从第 j j j 个位置开始往左不断更新,直到 ν ℓ ⩾ j \nu_\ell\geqslant j νℓ⩾j 。复杂度 O ( ∑ ν ℓ ) = O ( n n ) \mathcal O(\sum\nu_\ell)=\mathcal O(n\sqrt{n}) O(∑νℓ)=O(nn) 是正确的。
右端点的移动不连续,很恼火。所以我们不要先枚举 c c c,我们就直接移动 r r r,得到 c = a r + 1 c=a_{r+1} c=ar+1 也可以算答案(比如将询问离线)。右端点移动时,显然每个等值段顶多有个前缀的值变大;暴力枚举 c c c 的最末 n \sqrt{n} n 次出现,即可暴力更新。时间复杂度 O ( n n ) \mathcal O(n\sqrt{n}) O(nn) 。
当然,规定 a l − 1 = c a_{l-1}=c al−1=c 可能比将询问离线更好写。
代码
#include <cstdio> // I am paratrooper!!!
#include <iostream> // Almighty XJX yyds!!!
#include <algorithm> // oracle: ZXY yydBUS!!!
#include <cstring> // decent XYX yydLONELY!!!
#include <cctype> // Old-God DDG devoured me!!!
#include <vector>
typedef long long llong;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar()) if(c == '-') f = -f;
for(; isdigit(c); c=getchar()) a = a*10+(c^48);
return a*f;
}
inline void writeint(int x){
if(x > 9) writeint(x/10);
putchar(int(x%10)^48);
}
const int MAXN = 200005, SQRTN = 470;
int a[MAXN], tmp[MAXN], ansv;
std::vector<int> pos[MAXN], ansc;
inline void updateAnswer(int v, int c){
if(ansv < v) ansv = v, ansc.resize(1), ansc[0] = c;
else if(ansv == v) ansc.push_back(c);
}
int cnt[MAXN], major[MAXN];
inline void addColor(const int &col){
if(pos[col].size() > unsigned(SQRTN)) return;
for(int i=cnt[col]-1,j=1; ~i; --i,++j)
for(int k=pos[col][i]; k&&major[k]<j; --k)
major[k] = j; // brutely update
}
inline void check(const int &col){
if(pos[col].size() > unsigned(SQRTN)) return;
for(int i=cnt[col]-1,k=0; ~i; --i,++k){
const int v = major[pos[col][i]+1]-k;
updateAnswer(int(pos[col].size())+v,col);
}
}
bool ok[MAXN]; int s[MAXN];
int main(){
for(int T=readint(),n; T; --T){
n = readint(); rep(i,1,n) a[i] = readint();
memcpy(tmp+1,a+1,n<<2), std::sort(tmp+1,tmp+n+1);
int m = int(std::unique(tmp+1,tmp+n+1)-tmp-1);
rep(i,1,n) a[i] = int(std::lower_bound(tmp+1,tmp+m+1,a[i])-tmp);
rep(i,1,m) pos[i].resize(0); // several cases
rep(i,1,n) pos[a[i]].push_back(i); // ordered
ansv = 0, ansc.resize(0); // clear
rep(c,1,m) if(pos[c].size() > unsigned(SQRTN)){
memset(s+1,0,n<<2); // s[0] = 0
for(const int &i : pos[c]) ++ s[i];
rep(i,2,n) s[i] += s[i-1];
int nowv = 1; // range length = 1
rep(i,1,m) if(i != c){
int mn = 0, len = int(pos[i].size());
int mn2 = 0, thatv = 1; // i outside
for(int j=0; j!=len; ++j){
if(mn >= j-s[pos[i][j]]) mn = j-s[pos[i][j]];
else nowv = std::max(nowv,j+1-s[pos[i][j]]-mn);
if(mn2 >= s[pos[i][j]]-j) mn2 = s[pos[i][j]]-j-1;
else thatv = std::max(thatv,s[pos[i][j]]-j-mn2);
}
thatv = std::max(thatv,s[n]-len-mn2);
updateAnswer(thatv+len,i);
}
updateAnswer(nowv+s[n],c);
}
memset(cnt+1,0,m<<2), memset(major+1,0,n<<2);
for(int r=1; true; ++r){
++ cnt[a[r]], addColor(a[r]); if(r == n) break;
if(a[r] == a[r+1]) continue; // bad case
check(a[r+1]); // and maybe a prefix
updateAnswer(major[1]-cnt[a[r+1]]
+int(pos[a[r+1]].size()),a[r+1]);
}
rep(i,1,m) if(i != a[n]) check(i); // try all kinds
printf("%d\n",ansv); memset(ok+1,false,m);
for(const int &i : ansc) ok[i] = true;
rep(i,1,m) if(ok[i]) writeint(tmp[i]), putchar('\n');
// puts("--------"); fflush(stdout);
}
return 0;
}