题解:P2263 命运的彼方

题意

给定序列 hhh,求 min⁡x(min⁡l,l≤n−k+1∑i=ll+k−1∣x−hi∣)\min\limits_x\left(\min\limits_{l,l\le n-k+1} \sum_{i = l}^{l+k-1} |x - h_i|\right)xmin(l,l≤n−k+1min∑i=ll+k−1∣x−hi∣)。

做法

如果我有一个长度为 kkk 的序列 aaa,任取一个 xxx 求 ∑i=1k∣x−ai∣\sum_{i = 1}^k |x - a_i|∑i=1k∣x−ai∣,xxx 肯定取 aaa 的中位数。这非常简单,我们可以想象在坐标轴上有 kkk 个点,我们设定 xxx 在最左边,那么可以算出距离总和 ansansans。xxx 加 111,相当于 ans+=左边点数−右边点数ans += \text{左边点数} - \text{右边点数}ans+=左边点数−右边点数。那么 xxx 到达正中央(或中央两点之间)前,ansansans 单调递减(左边点数 <<< 右边点数),之后又单调递增(左边点数 >>> 右边点数)。所以当 xxx 为中位数时,ansansans 最小。

考虑枚举区间右端点 rrr,现在的问题是如何快速求出区间 (l,r)(l,r)(l,r) 的中位数以及答案。有很多种数据结构都可以在 O(log⁡n)O(\log n)O(logn) 复杂度内加入/删除一个数(因为显然要离散化),树状数组最好写,就用它好了。设一个区间 (l,r)(l,r)(l,r) 的中位数是 midmidmid,区间中小于 midmidmid 的数的个数是 cntcntcnt,小于 midmidmid 的数的和是 sumsumsum,区间的和是 uuu,区间的答案就是 2×mid×cnt−2×sum+u−k×mid2 \times mid \times cnt - 2 \times sum + u - k \times mid2×mid×cnt−2×sum+u−k×mid,随意计算并合并同类项即可。所以需要维护两个树状数组,一个维护一段前缀内数的个数,另一个维护一段前缀内数字的和。最大的问题是如何找到中位数。可以利用倍增的思想找中位数(利用树状数组的结构),做到 O(log⁡n)O(\log n)O(logn)。

代码

时间复杂度 O(nlog⁡n)O(n \log n)O(nlogn),1.11s。

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define rep(i,n) for(int i = 1;i <= n;++i)
#define pre(i,n) for(int i = n;~i;--i)
#define int long long
#define swap(x,y) (x ^= y ^= x ^= y)
#define debug cerr<<"Help!\n"
constexpr int N = 5e5 + 5,inf = 1e18;
int h[N],b[N],val[N],tot,n,k,pos,res = inf,u,mid,cnt,sum,tmp;
struct Fen{//树状数组
	#define lowbit(x) (x & -x)
	int c[N];
	inline void add(int x,int ad){
		for(;x <= N - 5;x += lowbit(x)) c[x] += ad;
	}
	inline int query(int x){
		int res = 0;
		for(;x;x ^= lowbit(x)) res += c[x];
		return res;
	}
	inline int find(int k){//找到树状数组内第 k 大
		int res = 0,nxt;
		pre(i,20){
			nxt = res + (1 << i);
			if(nxt <= N - 5 && c[nxt] < k) res = nxt,k -= c[nxt];
		}
		return res + 1;
	}
}fen[2];//fen[0] 维护数的个数,fen[1] 维护和
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0),cout.tie(0);
	cin>>n>>k,pos = (k + 1) >> 1;
	rep(i,n) cin>>h[i],b[i] = h[i];
	sort(b + 1,b + n + 1);
	tot = unique(b + 1,b + n + 1) - b - 1;//离散化
	rep(i,n){
		tmp = h[i],h[i] = lower_bound(b + 1,b + tot + 1,h[i]) - b;
		val[h[i]] = tmp;
		fen[0].add(h[i],1),fen[1].add(h[i],val[h[i]]),u += val[h[i]];//加入这个数
		if(i < k) continue;
		if(i ^ k) fen[0].add(h[i-k],-1),fen[1].add(h[i-k],-val[h[i-k]]),u -= val[h[i-k]];//删除不在区间内的数
		mid = fen[0].find(pos),cnt = fen[0].query(mid - 1),sum = fen[1].query(mid - 1);
		tmp = 2 * val[mid] * cnt - 2 * sum + u - k * val[mid];
		res = min(res,tmp);
	}
	cout<<res;
	cerr<<'\n'<<1.0 * clock() / CLOCKS_PER_SEC;
	return 0;
}
相关推荐
whn197717 小时前
在sqllog中排查达梦阻塞会话
数据结构
01二进制代码漫游日记17 小时前
C/C++中的内存区域划分
c语言·jvm·数据结构·学习
xiaoye-duck18 小时前
《算法题讲解指南:优选算法-链表》--51.两数相加,52.两两交换链表中的节点
数据结构·算法·链表
代码改善世界18 小时前
【数据结构】八大排序算法详解(C语言实现)|插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序、归并排序、计数排序
c语言·数据结构·排序算法
2501_9403152618 小时前
98验证二叉搜索树
java·数据结构·算法
仰泳的熊猫18 小时前
题目2266:蓝桥杯2015年第六届真题-打印大X
数据结构·c++·算法·蓝桥杯
cui_ruicheng19 小时前
C++ 数据结构:AVL树原理与实现
数据结构·c++
小龙报19 小时前
【数据结构与算法】环与相遇:链表带环问题的底层逻辑与工程实现
c语言·数据结构·c++·物联网·算法·链表·visualstudio
噜啦噜啦嘞好19 小时前
算法:双指针
数据结构
仟濹19 小时前
【算法打卡day20(2026-03-12 周四)算法:前缀和,二维前缀和,快慢指针,哈希表set使用技巧,哈希表map使用技巧】7个题
数据结构·算法