题目描述
N柱砖,希望有连续K柱的高度是一样的. 你可以选择以下两个动作 1:从某柱砖的顶端拿一块砖出来,丢掉不要了. 2:从仓库中拿出一块砖,放到另一柱.仓库无限大. 现在希望用最小次数的动作完成任务.
输入
第一行给出N,K. (1 ≤ k ≤ n ≤ 100000), 下面N行,每行代表这柱砖的高度.0 ≤ hi ≤ 1000000
输出
最小的动作次数
样例输入
5 3
3
9
2
3
1
样例输出
2
题解
Treap
根据某数学定理,∑|ai-x|最小时x为ai的中位数。
那么我们可以对每段可能的区间取一下中位数,再查找这个最小和。
具体实现方法可以使用Treap,同时维护si和sum。
求最小和时需要递归进行,比较x和v[k]的大小关系,如果有一整颗子树的值都小于或大于x,那么直接加上|sum-si*x|,并递归处理剩余部分。
时间好像有点长,大概5s,但这样的做法保证能过。
#include <cstdio> #include <cstdlib> #include <algorithm> using namespace std; typedef long long ll; int cnt[100010] , rnd[100010] , l[100010] , r[100010] , si[100010] , root , tot; ll a[100010] , v[100010] , sum[100010]; void pushup(int x) { si[x] = si[l[x]] + si[r[x]] + cnt[x]; sum[x] = sum[l[x]] + sum[r[x]] + v[x] * cnt[x]; } void zig(int &k) { int t = l[k]; l[k] = r[t]; r[t] = k; pushup(k); pushup(t); k = t; } void zag(int &k) { int t = r[k]; r[k] = l[t]; l[t] = k; pushup(k); pushup(t); k = t; } void ins(int &k , ll x) { if(!k) k = ++tot , v[k] = sum[k] = x , cnt[k] = si[k] = 1 , rnd[k] = rand(); else if(x == v[k]) cnt[k] ++ ; else if(x < v[k]) { ins(l[k] , x); if(rnd[l[k]] < rnd[k]) zig(k); } else { ins(r[k] , x); if(rnd[r[k]] > rnd[k]) zag(k); } pushup(k); } void del(int &k , ll x) { if(x == v[k] && cnt[k] <= 1) { if(l[k] * r[k] == 0) k = l[k] + r[k]; else if(rnd[l[k]] < rnd[r[k]]) zig(k) , del(k , x); else zag(k) , del(k , x); return; } if(x == v[k]) cnt[k] -- ; else if(x < v[k]) del(l[k] , x); else del(r[k] , x); pushup(k); } ll find(int k , int x) { if(x <= si[l[k]]) return find(l[k] , x); else if(x > si[l[k]] + cnt[k]) return find(r[k] , x - si[l[k]] - cnt[k]); else return v[k]; } ll query(int k , ll x) { if(!k) return 0; if(x == v[k]) return x * si[l[k]] - sum[l[k]] + sum[r[k]] - x * si[r[k]]; else if(x < v[k]) return (v[k] - x) * cnt[k] + sum[r[k]] - x * si[r[k]] + query(l[k] , x); else return (x - v[k]) * cnt[k] + x * si[l[k]] - sum[l[k]] + query(r[k] , x); } int main() { int n , k , i; ll ans = 0x7fffffffffffffffll; scanf("%d%d" , &n , &k); for(i = 1 ; i <= n ; i ++ ) scanf("%lld" , &a[i]); for(i = 1 ; i < k ; i ++ ) ins(root , a[i]); for(i = k ; i <= n ; i ++ ) { ins(root , a[i]); ans = min(ans , query(root , find(root , k / 2 + 1))) , del(root , a[i - k + 1]); } printf("%lld\n" , ans); return 0; }
时间: 2024-10-27 19:34:39