【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;
}
时间: 2024-10-27 19:34:39

【bzoj1112】[POI2008]砖块Klo Treap的相关文章

[BZOJ1112] [POI2008] 砖块Klo (treap)

Description N柱砖,希望有连续K柱的高度是一样的. 你可以选择以下两个动作 1:从某柱砖的顶端拿一块砖出来,丢掉不要了. 2:从仓库中拿出一块砖,放到另一柱.仓库无限大. 现在希望用最小次数的动作完成任务. Input 第一行给出N,K. (1 ≤ k ≤ n ≤ 100000), 下面N行,每行代表这柱砖的高度.0 ≤ hi ≤ 1000000 Output 最小的动作次数 Sample Input 5 3 3 9 2 3 1 Sample Output 2 HINT 原题还要求输

bzoj1112[POI2008]砖块Klo*

bzoj1112[POI2008]砖块Klo 题意: N柱砖,希望有连续K柱的高度是一样的. 你可以选择以下两个动作 1:丢掉某柱砖的一块砖.给某柱加上一块砖,现在希望用最小次数的动作完成任务.N≤100000 题解: 设一个区间长度为k,其中位数为a,比a小的元素个数为b,和为c:比a大的元素个数为d,和为e.则题目要求维护一个长度为k的滑动窗口,能求出它的b*a-c+e-d*a.故用一个维护sum,size两个值的treap来维护.然而似乎我想复杂了?比所有人代码都大1k!注意要开long

【枚举】【权值分块】bzoj1112 [POI2008]砖块Klo

枚举长度为m的所有段,尝试用中位数更新答案. 所以需要数据结构,支持查询k大,以及大于/小于 k大值 的数的和. 平衡树.权值线段树.权值分块什么的随便呢. 1 #include<cstdio> 2 #include<algorithm> 3 #include<cmath> 4 using namespace std; 5 typedef long long ll; 6 struct Point{int v,p;}t[100001]; 7 bool operator &

BZOJ 1112: [POI2008]砖块Klo

1112: [POI2008]砖块Klo Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1736  Solved: 606[Submit][Status][Discuss] Description N柱砖,希望有连续K柱的高度是一样的. 你可以选择以下两个动作 1:从某柱砖的顶端拿一块砖出来,丢掉不要了. 2:从仓库中拿出一块砖,放到另一柱.仓库无限大. 现在希望用最小次数的动作完成任务. Input 第一行给出N,K. (1 ≤ k ≤ n ≤

1112: [POI2008]砖块Klo

1112: [POI2008]砖块Klo Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1245  Solved: 426[Submit][Status][Discuss] Description N柱砖,希望有连续K柱的高度是一样的. 你可以选择以下两个动作 1:从某柱砖的顶端拿一块砖出来,丢掉不要了. 2:从仓库中拿出一块砖,放到另一柱.仓库无限大. 现在希望用最小次数的动作完成任务. Input 第一行给出N,K. (1 ≤ k ≤ n ≤

BZOJ 1112 [POI2008]砖块Klo(可持久化线段树)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1112 [题目大意] 给出一个数列,对于一个操作,你可以对一个数+1,或者一个数-1, 问若使得数列中出现长度为m的连续相同的数,最少需要的操作数. [题解] 我们发现对于固定区间求最小操作,等价于求区间中数距离中位数差值和最小, 我们发现区间中位数可以利用主席树求区间kth来实现, 同时在主席树上维护权值线段树的区间真值和,那么对于每个区间中的数, 就能分别维护比中位数小的部分的和以

[BZOJ 1112] [POI2008] 砖块Klo 【区间K大】

题目链接:BZOJ - 1112 题目分析 枚举每一个长度为k的连续区间,求出这个区间的最优答案,更新全局答案. 可以发现,这个区间的所有柱子最终都变成这k个数的中位数时最优,那么我们就需要查询这个区间的中位数了. 找到中位数之后,我们还应该求出这个区间内小于中位数的数的和,大于中位数的数的和,从而求出操作步数. 这些需要求的值可以用线段树或平衡树来写,我写的是线段树,但是实际上这是一道POI的题目,在MAIN上的空间限制只有35MB,线段树应该是不行的. 因为平衡树只需要 O(n) 空间,所以

BZOJ 1112: [POI2008]砖块Klo1112( BST )

枚举每个长度为k的区间, 然后用平衡树找中位数进行判断, 时间复杂度O(nlogn). 早上起来精神状态不太好...连平衡树都不太会写了...果断去看了会儿番然后就A了哈哈哈 -------------------------------------------------------------------------- #include<bits/stdc++.h> #define rep(i, n) for(int i = 0; i < n; i++) #define clr(x,

bzoj-1112 砖块Klo

题意: 给出一个长度为n的数列,现要将其连续k个数变成一样的: 每次可以对一个数+1或-1,问最小操作次数: 1<=k<=n<=100000,0<=数列中的数<=1000000: 题解: 感觉是一道好题吧: 首先有这样一个结论:将这些数置为中位数所需要的操作数最小: 证明啥的网上关于中位数的一大堆?反正我不会: 然后我们要做的就是: 1.动态维护区间中位数: 2.对一个区间更新答案: 有的同学选择了平衡树解决.. 实际上这个题用权值树状数组就好了,虽说多个log= =: 中位