【洛谷P3466】KLO-Building blocks

题目

题目链接:https://www.luogu.com.cn/problem/P3466
给出一个数列,将一个数加一或减一都付出\(1\)的代价。求最小代价使得数列中存在一个长度为\(m\)的子序列每一个数字都相同。

思路

很明显就是枚举每一个长度为\(m\)的区间,然后求中位数搞一搞。
求中位数可以用对顶堆。我们求出中位数后,计算小于中位数的数字之和以及大于中位数的数字之和,就可以计算出答案。
但是这样我们从区间\([i,i+m-1]\)转移到\([i+1,i+m]\)时,需要支持在堆中 删除任意元素,而这是\(STL\)自带的堆没有的功能。而又不想手写堆(
所以我们需要一个能在\(O(\log n)\)的时间复杂度内支持求最值,插入,删除的数据结构。
那么平衡树就可以了。
用平衡树代替堆,维护两棵平衡树即可。
当然这是比较暴力的方法,维护一棵平衡树也是可以的,因为平衡树支持查找中位数(大概吧)。
时间复杂度\(O(n\log m)\)
其实写这道题就是想默一下Treap的板子233

代码

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;

const int N=800010,Inf=1e9;
int n,m,pos,v,a[N];
ll sum,ans=1e17;

struct Treenode
{
	int lc,rc,size,dat,val,cnt;
};

struct Treap
{
	Treenode tree[N];
	int tot,root;
	ll sum;
	
	int New(int x)
	{
		tree[++tot].val=x;
		tree[tot].size=tree[tot].cnt=1;
		tree[tot].dat=rand();
		return tot;
	}
	
	void pushup(int x)
	{
		tree[x].size=tree[tree[x].lc].size+tree[tree[x].rc].size+tree[x].cnt;
	}
	
	void build()
	{
		root=New(-Inf);
		tree[1].rc=New(Inf);
		pushup(1);
	}
	
	void zig(int &x)
	{
		int y=tree[x].lc;
		tree[x].lc=tree[y].rc; tree[y].rc=x; x=y;
		pushup(tree[x].rc); pushup(x);
	}
	
	void zag(int &x)
	{
		int y=tree[x].rc;
		tree[x].rc=tree[y].lc; tree[y].lc=x; x=y;
		pushup(tree[x].lc); pushup(x);
	}
	
	int Min(int x)  //求平衡树中最小值,最大值下同
	{
		if (!x) return Inf;
		if (tree[x].val==-Inf) return Min(tree[x].rc);
		return min(Min(tree[x].lc),tree[x].val);
	}
	
	int Max(int x)
	{
		if (!x) return -Inf;
		if (tree[x].val==Inf) return Max(tree[x].lc);
		return max(Max(tree[x].rc),tree[x].val);
	}
	
	void insert(int &x,int val)
	{
		if (x==root) sum+=val;
		if (!x) x=New(val);
		else if (tree[x].val==val) tree[x].cnt++;
		else if (val<tree[x].val)
		{
			insert(tree[x].lc,val);
			if (tree[tree[x].lc].dat<tree[x].dat) zig(x);
		}
		else
		{
			insert(tree[x].rc,val);
			if (tree[tree[x].rc].dat<tree[x].dat) zag(x);
		}
		pushup(x);
	}
	
	void del(int &x,int val)
	{
		if (x==root) sum-=val;
		if (tree[x].val==val)
		{
			if (tree[x].cnt>1) tree[x].cnt--;
			else if (tree[x].lc || tree[x].rc)
			{
				if (!tree[x].lc || tree[tree[x].lc].dat<tree[tree[x].rc].dat)
					zag(x),del(tree[x].lc,val);
				else
					zig(x),del(tree[x].rc,val);
			}
			else x=0;
		}
		else if (tree[x].val>val) del(tree[x].lc,val);
		else del(tree[x].rc,val);
		pushup(x);
	}
	
	bool find(int x,int val)  //查找平衡树中是否有val这个值
	{
		if (!x) return 0;
		if (tree[x].val==val) return 1;
		if (tree[x].val>val) return find(tree[x].lc,val);
			else return find(tree[x].rc,val);
	}
}Treap1,Treap2;

void update()  //维护两棵。。。对顶平衡树?(雾
{
	while (Treap1.Max(Treap1.root)>Treap2.Min(Treap2.root))
	{
		int x=Treap1.Max(Treap1.root);
		Treap1.del(Treap1.root,x); Treap2.insert(Treap2.root,x);
	}
	while (Treap1.tree[Treap1.root].size-Treap2.tree[Treap2.root].size>=2)
	{
		int x=Treap1.Max(Treap1.root);
		Treap1.del(Treap1.root,x); Treap2.insert(Treap2.root,x);
	}
	while (Treap2.tree[Treap2.root].size>Treap1.tree[Treap1.root].size)
	{
		int x=Treap2.Min(Treap2.root);
		Treap2.del(Treap2.root,x); Treap1.insert(Treap1.root,x);
	}
}

int main()
{
	scanf("%d%d",&n,&m);
	srand(n*2333-m*666);  //优秀的重置种子方法
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	Treap1.build(); Treap2.build();
	for (int i=1;i<m;i++)
	{
		Treap1.insert(Treap1.root,a[i]);
		update();
	}
	for (int i=m;i<=n;i++)
	{
		Treap1.insert(Treap1.root,a[i]);
		update();
		ll mid=1LL*Treap1.Max(Treap1.root);
		int cnt1=Treap1.tree[Treap1.root].size,cnt2=Treap2.tree[Treap2.root].size;
		ll cost=mid*cnt1-Treap1.sum+Treap2.sum-mid*cnt2;  //计算代价
		if (cost<ans)
			ans=cost,pos=i-m+1,v=mid;
		if (Treap1.find(Treap1.root,a[i-m+1])) Treap1.del(Treap1.root,a[i-m+1]);
			else Treap2.del(Treap2.root,a[i-m+1]);
		update();
	}
	printf("%lld\n",ans);
	for (int i=1;i<=n;i++)
		if (i>=pos && i<pos+m) printf("%d\n",v);
			else printf("%d\n",a[i]);
	return 0;
}
posted @ 2020-01-28 12:04  stoorz  阅读(156)  评论(0编辑  收藏  举报