区间第 k 大(计数+链表)

NOIP2021 模拟赛,\(\text{tyy}\) 的题,爆大零,打了个水分暴力就跳了。

题目内容

给定一个 \(1\sim n\) 的排列 \(\{p_i\}\) 以及整数 \(k\)

对于每个 \(i \in [1,n]\),你需要求出该排列中有多少个区间 \([l,r]\) 的第 \(k\) 大恰好是 \(p_i\)

\(1\leq n\leq 3\times10^5\)\(1\leq k\leq 100\)

解题思路

对每一个 \(p_i\) 求有多少个区间满足条件。在符合条件的区间 \([l,r]\) 内,如果 \(p_i\) 左边有 \(j\) 个数大于它,那么在右边显然有 \(k-j-1\) 个数大于它。所以枚举 \(j\),设 \(f_j\) 表示 \(p_i\) 左边比 \(p_i\) 大的第 \(j\) 个数的位置,\(g_j\) 表示 \(p_i\) 右边比 \(p_i\) 大的第 \(j\) 个数的位置,则每个 \(j\) 对答案的贡献为 \((f_j-f_{j-1})\times (g_{k-j}-g_{k-j-1})\)

要预处理 \(f,g\) 两个数组,也就是要找到 \(p_i\) 左右各 \(k\) 个比它大的数。如果你 \(\mathcal{O(n^2)}\) 的做这件事只能拿 \(50\) 分,如果用线段树/平衡树可以带个 \(\log\) 草过去(或者被卡

\(\mathcal{O(nk)}\) 怎么做?我们需要一个数据结构来替代线段树,去实现 \(\mathcal{O(1)}\) 删除、查询前驱后继——链表!从小到大枚举 \(p_i\)(因为是个排列),跳前驱后继处理 \(f,g\) 记录答案,删除 \(p_i\) 保证当前元素最小即可。

AC 代码

代码基本按照 std 思路写的。

for(int i=1;i<=n;++i){
	int at=b[i];//b[]为每个值的编号
	for(int j=0;j<=k;++j) f[j]=at,at=L[at];//跳前驱处理f[]
	at=b[i]; for(int j=0;j<=k;++j) g[j]=at,at=R[at];//跳后继处理g[]
	at=b[i]; for(int j=1;j<=k;++j) ans[at]+=(lint)(f[j]-f[j-1])*(g[k-j]-g[k-j+1]);//记录答案
	L[R[at]]=L[at],R[L[at]]=R[at];//删除节点
}

THE END

posted @ 2021-11-15 16:44  q0000000  阅读(45)  评论(0编辑  收藏  举报