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

题目链接:BZOJ - 1112

题目分析

枚举每一个长度为k的连续区间,求出这个区间的最优答案,更新全局答案。

可以发现,这个区间的所有柱子最终都变成这k个数的中位数时最优,那么我们就需要查询这个区间的中位数了。

找到中位数之后,我们还应该求出这个区间内小于中位数的数的和,大于中位数的数的和,从而求出操作步数。

这些需要求的值可以用线段树或平衡树来写,我写的是线段树,但是实际上这是一道POI的题目,在MAIN上的空间限制只有35MB,线段树应该是不行的。

因为平衡树只需要 O(n) 空间,所以平衡树才是正解。

代码

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdio>

using namespace std;

const int MaxN = 100000 + 5, MaxNode = 100000 * 20 + 15, MN = 1000000 + 5;

typedef long long LL;

int n, k, Index, Root;
int A[MaxN], T[MaxNode], Son[MaxNode][2];

const LL INF = 999999999999;

LL Ans;
LL Sum[MaxNode];

inline LL gmin(LL a, LL b) {return a < b ? a : b;}

inline void Read(int &Num)
{
	char c; c = getchar();
	while (c < ‘0‘ || c > ‘9‘) c = getchar();
	Num = c - ‘0‘; c = getchar();
	while (c >= ‘0‘ && c <= ‘9‘)
	{
		Num = Num * 10 + c - ‘0‘;
		c = getchar();
	}
}

void Add(int &x, int s, int t, int Pos, int Num)
{
	if (x == 0) x = ++Index;
	T[x] += Num;
	Sum[x] += (LL)Pos * (LL)Num;
	if (s == t) return;
	int m = (s + t) >> 1;
	if (Pos <= m) Add(Son[x][0], s, m, Pos, Num);
	else Add(Son[x][1], m + 1, t, Pos, Num);
}

int Kth(int x, int s, int t, int k)
{
	if (s == t) return s;
	int ret, m = (s + t) >> 1;
	if (T[Son[x][0]] >= k) ret = Kth(Son[x][0], s, m, k);
	else ret = Kth(Son[x][1], m + 1, t, k - T[Son[x][0]]);
	return ret;
}

LL GetSum(int x, int s, int t, int l, int r)
{
	if (l <= s && r >= t) return Sum[x];
	int m = (s + t) >> 1;
	LL ret = 0ll;
	if (l <= m && Son[x][0]) ret += GetSum(Son[x][0], s, m, l, r);
	if (r >= m + 1 && Son[x][1]) ret += GetSum(Son[x][1], m + 1, t, l, r);
	return ret;
}

int GetNum(int x, int s, int t, int l, int r)
{
	if (l <= s && r >= t) return T[x];
	int m = (s + t) >> 1;
	int ret = 0;
	if (l <= m && Son[x][0]) ret += GetNum(Son[x][0], s, m, l, r);
	if (r >= m + 1 && Son[x][1]) ret += GetNum(Son[x][1], m + 1, t, l, r);
	return ret;
}

int main()
{
	scanf("%d%d", &n, &k);
	for (int i = 1; i <= n; ++i) Read(A[i]);
	Root = Index = 0;
	A[0] = 0;
	for (int i = 0; i <= k - 1; ++i) Add(Root, 0, MN, A[i], 1);
	Ans = INF;
	int t = k / 2 + 1, Temp;
	LL Now;
	for (int i = k; i <= n; ++i)
	{
		Add(Root, 0, MN, A[i - k], -1);
		Add(Root, 0, MN, A[i], 1);
		Temp = Kth(Root, 0, MN, t);
		Now = (LL)GetNum(Root, 0, MN, 0, Temp - 1) * (LL)Temp - GetSum(Root, 0, MN, 0, Temp - 1);
		Now += GetSum(Root, 0, MN, Temp + 1, MN) - (LL)GetNum(Root, 0, MN, Temp + 1, MN) * (LL)Temp;
		Ans = gmin(Ans, Now);
	}
	printf("%lld\n", Ans);
	return 0;
}

  

时间: 2024-10-19 03:32:15

[BZOJ 1112] [POI2008] 砖块Klo 【区间K大】的相关文章

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 ≤

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

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

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]砖块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 1901: Zju2112 Dynamic Rankings 区间k大 带修改 在线 线段树套平衡树

之前写线段树套splay数组版..写了6.2k..然后弃疗了.现在发现还是很水的..嘎嘎.. zju过不了,超时. upd:才发现zju是多组数据..TLE一版才发现.然后改了,MLE...手写内存池..尼玛终于过了..附zju2112代码于后. bzoj倒是过了,1A的感觉还是很爽的..可是时间不好看..这就是所谓\(O(nlog^3n)\)的复杂度的可怜之处么? 写挂的地方: insert一定要是传地址指针进去. delete时先把地址指针delete掉,最后把是地址指针指向左儿子or右儿子

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

poj2104 划分树 区间K大 在线 无修改

博主sbit....对于高级数据结构深感无力,然后这些东西在OI竟然烂大街了,不搞就整个人都不好了呢. 于是我勇猛的跳进了这个大坑 ——sbit 区间K大的裸题,在线,无修改. 可以用归并树(\(O(nlog^3n)\)),也可用划分树(\(O(nlogn + mlogn)\)).果断划分树...(以后再来看归并树...再来看...来看..看..) 划分树是个什么东西呢?为什么可以做区间k大呢? 想想平衡树做k大时是如何搞的,其实内在原理是一样的. 划分树分两个步骤:建树与询问,这两个步骤相互关

poj2104 主席树 区间K大 在线 无修改

关于主席树: 主席树(Chairman Tree)是一种离线数据结构,使用函数式线段树维护每一时刻离散之后的数字出现的次数,由于各历史版本的线段树结构一致,可以相减得出区间信息,即该区间内出现的数字和对应的数量,由于在线段树内,左子树代表的数字都小与右子树,便可像平衡树一样进行K大询问.新建一颗树是\(O(logn)\),查询一次也为\(O(logn)\). 比划分树好想&写多了,但是在POJ上比划分树慢一些. CODE: 1 #include <cstdio> 2 #include

【区间k大】HDU 54512 CRB and Queries

通道 题意:区间k大,单点修改 思路:裸,复杂度n(lgn)^2 代码: #include <cstdio> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> using namespace std; const int N = 60010 * 5; const int M = 100100 * 4; #define INF 1000000