[ZJOI2022]众数

137 篇文章 1 订阅
13 篇文章 0 订阅

题目

题目背景
校长使用「技能:发链接」, 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 n5×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 al1=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;
}

  1. 不枚举,怎么能找到 c c c 的数量呢?这么显然的逻辑, O n e I n D a r k \sf OneInDark OneInDark 却意识不到! ↩︎

  2. 不出所料,愚蠢的 O n e I n D a r k \sf OneInDark OneInDark 连这个都没注意到。 ↩︎

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值