可持久化摸鱼(桶排优化)

原题 SDFZ-NOIP2021模拟11-T2 韭菜收割,改成 \(\text{zyd & lyn}\) 摸鱼扔到洛谷 U188872

PS: 可持久化摸鱼是今年我们 ICPC 济南站打星队的队名,所以就这么起名了。

前 50 分很送,\(\mathcal{O(kn\log n)}\) 我一直以为过不去 \(n\le 2000,k\le 2000\),然而开 O2 跑得飞快。有一个 \(a_i\le 2\) 的部分分,可以直接开桶统计 \(2\) 的个数。

考虑正解。暴力中,区间是怎样向右扩展的?进来一个 \(a_i\),直接扔进堆里,然后取堆顶元素对吧?还是说加一个剪枝——与堆顶比较,如果更大则直接加入答案,不再进堆,否则扔进去?后者直接指向一个关键结论:随着区间向右扩展,其最大值单调不增! 更大的不会进堆(直接加了),原来的最大值会被取出,更小的元素成为最大值……正确性显然。

特别地,最后只出不入的阶段,可以等价为加了一堆 \(0\),仍然不增。

利用这个结论,我们不难创造一个算法:开桶记录 \(1\to p-1\) 每个数值出现的次数,暴力跑到当前最大值位置 \(cur\);继续枚举后面的 \(a_i\),仍然是更大直接加,否则扔进桶里,取出最大值加入答案。注意,这时“取出最大值”不再是拿堆顶,而是 cnt[cur]--;

分析时间复杂度:对于每次询问从 \(1\)\(n\) 枚举,\(cur\) 同时从 \(n\) 减小到 \(1\),显然 \(\mathcal{O(kn)}\)

下面是 AC 代码:

int fnd(){ while(!cnt[cur]) cur--; return cur; }//查找当前最大值 

int main(){
	int n=inn(),k=inn(); rep(i,1,n) a[i]=inn();
	while(k--){
		int p=inn(); rep(i,1,p-1) cnt[a[i]]++;
		cur=n,fnd(); lint ans=0;
		rep(i,1,n)
			if(i+p-1<=n)
				if(a[i+p-1]>=fnd()) ans+=(i&1)*a[i+p-1];//插入元素更大,直接加 
				else cnt[a[i+p-1]]++,ans+=(i&1)*cur,cnt[cur]--;//取max加,新元素入桶 
			else ans+=(i&1)*fnd(),cnt[cur]--;//等效于加0,直接取max加 
		printf("%lld\n",ans);
	}
	return 0;
}

THE END

posted @ 2021-11-12 20:49  q0000000  阅读(36)  评论(0编辑  收藏  举报