【bzoj1112】[POI2008]砖块Klo Treap

题目描述

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;
}

 

posted @ 2017-04-01 14:31  GXZlegend  阅读(293)  评论(0编辑  收藏  举报