BZOJ4504 k个串

71 篇文章 0 订阅
15 篇文章 0 订阅

Description

兔子们在玩k个串的游戏。首先,它们拿出了一个长度为n的数字序列,选出其中的一
个连续子串,然后统计其子串中所有数字之和(注意这里重复出现的数字只被统计一次)。
兔子们想知道,在这个数字序列所有连续的子串中,按照以上方式统计其所有数字之和,第
k大的和是多少。

Input

第一行,两个整数n和k,分别表示长度为n的数字序列和想要统计的第k大的和
接下里一行n个数a_i,表示这个数字序列

Output

一行一个整数,表示第k大的和

Sample Input

7 5
3 -2 1 2 2 1 3 -2

Sample Output

4

HINT

1 <= n <= 100000, 1 <= k <= 200000, 0 <= |a_i| <= 10^9数据保证存在第 k 大的和

题意

给出一个数字序列,求第k大的去重之后的连续子序列的和.

做法

这题与[NOI2010]超级钢琴很像,但这题还需要去重.
我们可以根据右端点的位置来排序,这样如果一个点对这段区间有贡献.那么左端点就一定在该点上一次出现位置(如未出现,则为0)+1与该点位置之间,而且以第i个点为右端点与以第i+1个点为右端点的区间仅需考虑新加入的i+1点,[NOI2010]超级钢琴中,我们利用st表来维护区间最大值,这题则可以用线段树来维护,再利用右端点的移动对答案的影响很小的性质,可以建出主席树,加入点时,利用永久化标记(永远不会向子节点传递的标记,因为只需要维护区间最大值的位置,故可以打上这个标记)可以保证只增加log个的节点.
最后用优先队列维护,将结构体(l,r,mx,mx_pos,from)放入堆,这个结构体表示右端点为from(第from棵树),左端点在l~r时的最大值为mx,最大值的位置为max _pos,这样之后将结构体(l,max _pos-1,l~max _pos-1间的最大值及其位置)和结构体(max _pos+1,r,max _pos+1~r间的最大值及其位置)放入堆中,重复k-1次,堆顶即为答案.

代码

#include<iostream>
#include<cstdio>
#include<queue>
#include<map>
#define ll long long
#define P pair<ll,ll>
#define mp make_pair
#define fi first
#define se second
#define mid ((l+r)>>1)
#define N 100100
using namespace std;

ll n,m,root[N],tt;
P tp;
struct Node
{
	ll max_pos,sum,left_son,right_son,mx;
}node[N*200];
struct Pm
{
	ll left,right,max_pos,from,mx;
	bool operator < (const Pm &u) const
	{
		return mx<u.mx;
	}
}tmp,t2;
map<ll,ll>pre;
priority_queue<Pm>pq;

void build(ll now,ll l,ll r)
{
	node[now].max_pos=l;
	if(l==r) return;
	node[now].left_son=++tt;
	build(tt,l,mid);
	node[now].right_son=++tt;
	build(tt,mid+1,r);
}

void add(ll now,ll l,ll r,ll al,ll ar,ll u)
{
	if(l==al&&r==ar)
	{
		node[now].sum+=u;
		return;
	}
	if(mid>=ar)
	{
		node[++tt]=node[node[now].left_son]; 
		node[now].left_son=tt;
		add(tt,l,mid,al,ar,u);
	}
	else if(mid<al)
	{
		node[++tt]=node[node[now].right_son];
		node[now].right_son=tt;
		add(tt,mid+1,r,al,ar,u);
	}
	else
	{
		node[++tt]=node[node[now].left_son];
		node[now].left_son=tt;
		add(tt,l,mid,al,mid,u);
		node[++tt]=node[node[now].right_son];
		node[now].right_son=tt;
		add(tt,mid+1,r,mid+1,ar,u);
	}
	if(node[node[now].left_son].sum+node[node[now].left_son].mx>node[node[now].right_son].sum+node[node[now].right_son].mx)
	{
		node[now].mx=node[node[now].left_son].sum+node[node[now].left_son].mx;
		node[now].max_pos=node[node[now].left_son].max_pos;
	}
	else
	{
		node[now].mx=node[node[now].right_son].sum+node[node[now].right_son].mx;
		node[now].max_pos=node[node[now].right_son].max_pos;
	}
}

P ask(ll now,ll l,ll r,ll al,ll ar)
{
	if(l==al&&r==ar)
	{
		return mp(node[now].mx+node[now].sum,node[now].max_pos);
	}
	if(mid>=ar)
	{
		P res=ask(node[now].left_son,l,mid,al,ar);
		res.fi+=node[now].sum;
		return res;
	}
	else if(mid<al)
	{
		P res=ask(node[now].right_son,mid+1,r,al,ar);
		res.fi+=node[now].sum;
		return res;
	}
	else
	{
		P res=max(ask(node[now].left_son,l,mid,al,mid),ask(node[now].right_son,mid+1,r,mid+1,ar));
		res.fi+=node[now].sum;
		return res;
	}
}

int main()
{
	ll i,j,p,q;
	cin>>n>>m;
	build(0,1,n);
	for(i=1;i<=n;i++)
	{
		root[i]=++tt;
		node[tt]=node[root[i-1]];
		scanf("%lld",&p);
		add(root[i],1,n,pre[p]+1,i,p);
		pre[p]=i;
	}
	for(i=1;i<=n;i++)
	{
		tmp.from=i;
		tmp.left=1;
		tmp.right=i;
		tp=ask(root[i],1,n,1,i);
		tmp.mx=tp.fi;
		tmp.max_pos=tp.se;
		pq.push(tmp);
	}
	for(i=1;i<m;i++)
	{
		tmp=pq.top();
		pq.pop();
		t2=tmp;
		t2.right=t2.max_pos-1;
		if(t2.left<=t2.right)
		{
			tp=ask(root[t2.from],1,n,t2.left,t2.right);
			t2.mx=tp.fi;
			t2.max_pos=tp.se;
			pq.push(t2);
		}
		t2=tmp;
		t2.left=tmp.max_pos+1;
		if(t2.left<=t2.right)
		{
			tp=ask(root[t2.from],1,n,t2.left,t2.right);
			t2.mx=tp.fi;
			t2.max_pos=tp.se;
			pq.push(t2);
		}
	}
	cout<<pq.top().mx;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值