【BZOJ4504】K个串(优先队列,可持久化线段树)

Description

兔子们在玩k个串的游戏。首先,它们拿出了一个长度为n的数字序列,选出其中的一个连续子串,然后统计其子串中所有数字之和(注意这里重复出现的数字只被统计一次)。

兔子们想知道,在这个数字序列所有连续的子串中,按照以上方式统计其所有数字之和,第k大的和是多少。

Input

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

接下里一行n个数a_i,表示这个数字序列

Output

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

f ( i , j ) f(i,j) f(i,j) 表示以 i i i 为左端点, j j j 为右端点的子串和。

对于每一个左端点 i i i,我们可以先把 f ( i , j ) f(i,j) f(i,j) 里面最大的那个放入优先队列(从大到小排序)中,接下来进行 k − 1 k-1 k1 次这样的操作:

先取出队首并弹出,设其所对应的是 f ( a , b ) f(a,b) f(a,b),那么我们可以找到 f ( a , j ) f(a,j) f(a,j) 中比 f ( a , b ) f(a,b) f(a,b) 小的最大的那个,并把它加入队列。也就是说,找到以 a a a 为左端点的 f ( a , j ) f(a,j) f(a,j) 次大值,扔进优先队列。

这样的 k − 1 k-1 k1 次操作结束后,队首就是我们要找的那个 k k k 大值了。

至于为什么,自己模拟一下或自己想一想。

现在的关键是如何维护最大值和次大值。

n x t i nxt_i nxti 表示数 a i a_i ai i i i 之后出现的第一个位置,显然可以 O ( n ) O(n) O(n) 预处理。

显然, f ( 1 ) f(1) f(1) 可以 O ( n ) O(n) O(n) 维护出来。

考虑如何从 f ( 1 ) f(1) f(1) 转移出 f ( 2 ) f(2) f(2)

  1. j = 1 j=1 j=1 时, f ( 2 , j ) f(2,j) f(2,j) 不存在,设为 − ∞ -\infty

  2. 2 ≤ j < n x t 1 2\leq j < nxt_1 2j<nxt1 时,如下图:

    在这里插入图片描述
    n x t 1 = 5 nxt_1=5 nxt1=5,不妨设 a 1 = a 5 = 10 a_1=a_5=10 a1=a5=10,那么由 n x t nxt nxt 的定义可知,当 2 ≤ j < n x t 1 2\leq j < nxt_1 2j<nxt1 时, a 2 ∼ a j a_2\sim a_j a2aj 中不可能存在一个数是 10 10 10。那么从 f ( 1 , j ) f(1,j) f(1,j) 更新到 f ( 2 , j ) f(2,j) f(2,j) 时,要减去 10 10 10 f ( 2 , j ) f(2,j) f(2,j) 的贡献。

    比如当 j = 4 j=4 j=4 时, a 1 ∼ a 4 a_1\sim a_4 a1a4 中出现的数有: 1 1 1 2 2 2 10 10 10,所以 f ( 1 , 4 ) = 1 + 2 + 10 = 13 f(1,4)=1+2+10=13 f(1,4)=1+2+10=13(如图红框),而 a 2 ∼ a 4 a_2\sim a_4 a2a4 a 1 = 10 a_1=10 a1=10 被去掉了,而 a 2 ∼ a 4 a_2\sim a_4 a2a4 中又没有重新出现过 10 10 10,所以 f ( 2 , 4 ) = f ( 1 , 4 ) − 10 = 3 f(2,4)=f(1,4)-10=3 f(2,4)=f(1,4)10=3(如图蓝框)。

  3. j ≥ n x t 1 j\geq nxt_1 jnxt1 时,如下图:
    在这里插入图片描述
    我们用同样的一个例子。观察发现:从 f ( 1 , j ) f(1,j) f(1,j) 转移到 f ( 2 , j ) f(2,j) f(2,j) 的时候只是去掉了 a 1 = 10 a_1=10 a1=10,但是当 j ≥ n x t 1 j\geq nxt_1 jnxt1 时,可以保证在 a 2 ∼ a j a_2\sim a_j a2aj 之间肯定会出现 10 10 10,所以 10 10 10 还是对 f ( 2 , j ) f(2,j) f(2,j) 有贡献,也就是说始终有 f ( 2 , j ) = f ( 1 , j ) f(2,j)=f(1,j) f(2,j)=f(1,j)

    比如当 j = 7 j=7 j=7 时, a 1 ∼ a 7 a_1\sim a_7 a1a7 出现的数有 1 1 1 2 2 2 4 4 4 10 10 10 f ( 1 , 7 ) = 1 + 2 + 4 + 10 = 17 f(1,7)=1+2+4+10=17 f(1,7)=1+2+4+10=17,而 a 2 ∼ a 7 a_2\sim a_7 a2a7 中出现的数和 a 1 ∼ a 7 a_1\sim a_7 a1a7 中出现的数没有变化,所以 f ( 2 , 7 ) = f ( 1 , 7 ) = 17 f(2,7)=f(1,7)=17 f(2,7)=f(1,7)=17

综上所述,可以推出:

j < i − 1 j< i-1 j<i1 f ( i , j ) = f ( i − 1 , j ) = − ∞ f(i,j)=f(i-1,j)=-\infty f(i,j)=f(i1,j)=

j = i − 1 j=i-1 j=i1 时, f ( i , j ) = − ∞ f(i,j)=-\infty f(i,j)=

i ≤ j < n x t i − 1 i \leq j < nxt_{i-1} ij<nxti1 时, f ( i , j ) = f ( i − 1 , j ) − a i − 1 f(i,j)=f(i-1,j)-a_{i-1} f(i,j)=f(i1,j)ai1

j ≥ n x t i − 1 j\geq nxt_{i-1} jnxti1 时, f ( i , j ) = f ( i − 1 , j ) f(i,j)=f(i-1,j) f(i,j)=f(i1,j)

我们就可以发现, f ( i − 1 , j ) f(i-1,j) f(i1,j) f ( i , j ) f(i,j) f(i,j) 有一段区间是一样的( j < i − 1 j<i-1 j<i1 j ≥ n x t i − 1 j\geq nxt_{i-1} jnxti1),有一段区间是 f ( i , j ) f(i,j) f(i,j) 等于 f ( i − 1 , j ) f(i-1,j) f(i1,j) 减去一个相同的值( i ≤ j < n x t i − 1 i \leq j < nxt_{i-1} ij<nxti1),有一个特殊的位置是 f ( i , j ) = − ∞ f(i,j)=-\infty f(i,j)= j = i − 1 j=i-1 j=i1)。

容易联想到用可持久化线段树维护 f ( i , j ) f(i,j) f(i,j)。( n n n 棵线段树,每棵线段树 i i i 存储 f ( i , j ) f(i,j) f(i,j) 及它们的最大值)。

代码及注释如下:

#include<bits/stdc++.h>

#define N 100010
#define lc t[u].ch[0]
#define rc t[u].ch[1]
#define ll long long
#define int long long
#define LNF 1e15

using namespace std;

inline int read()
{
	int x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<1)+(x<<3)+(ch^'0');
		ch=getchar();
	}
	return x*f;
}

struct data
{
	ll val;
	int rt,id;
	data(){};
	data(ll vall,int rtt,int idd){val=vall,rt=rtt,id=idd;}
	bool operator < (const data &a) const
	{
		return val<a.val;
	}
};

struct Segment_Tree
{
	int ch[2],where;
	ll maxn,tag;
}t[N*100];

int n,nn,k,a[N],b[N],last[N],nxt[N];
int node,root[N];
ll f[N];
bool vis[N];

priority_queue<data>q;

void up(int u)
{
	if(t[lc].maxn-t[lc].tag>t[rc].maxn-t[rc].tag)
	{
		t[u].maxn=t[lc].maxn-t[lc].tag;
		t[u].where=t[lc].where;
	}
	else 
	{
		t[u].maxn=t[rc].maxn-t[rc].tag;
		t[u].where=t[rc].where;
	}
}

void build(int &u,int l,int r)
{
	u=++node;
	if(l==r)
	{
		t[u].maxn=f[l];
		t[u].where=l;
		return;
	}
	int mid=(l+r)>>1;
	build(lc,l,mid);
	build(rc,mid+1,r);
	up(u);
}

void update(int &u,int last,int l,int r,int ql,int qr,ll x)
{
	u=++node;
	t[u]=t[last];
	if(ql<=l&&r<=qr)
	{
		t[u].tag+=x;
		return;
	}
	int mid=(l+r)>>1;
	if(ql<=mid) update(lc,t[last].ch[0],l,mid,ql,qr,x);
	if(qr>mid) update(rc,t[last].ch[1],mid+1,r,ql,qr,x);
	up(u);
}

void change(int u,int l,int r,int x,ll y)
{
	if(l==r)
	{
		t[u].maxn=y;
		t[u].tag=0;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid) change(lc,l,mid,x,y);
	else change(rc,mid+1,r,x,y);
	up(u);
}

signed main()
{
	n=read(),k=read();
	for(int i=1;i<=n;i++) 
		b[i]=a[i]=read();
	sort(b+1,b+n+1);
	nn=unique(b+1,b+n+1)-b-1;
	for(int i=1;i<=n;i++)
		a[i]=lower_bound(b+1,b+nn+1,a[i])-b;
	//以上是离散化
	for(int i=1;i<=n;i++) nxt[i]=n+1;
	for(int i=1;i<=n;i++)
	{
		if(last[a[i]]) nxt[last[a[i]]]=i;
		last[a[i]]=i;
	}
	//以上是预处理nxt
	for(int i=1;i<=n;i++)
	{
		if(!vis[a[i]])
		{
			vis[a[i]]=1;
			f[i]=f[i-1]+b[a[i]];
		}
		else f[i]=f[i-1];
	}
	//预处理f[1][i]
	build(root[1],1,n);//建出第一棵线段树
	for(int i=2;i<=n;i++)
	{
		if(i<=nxt[i-1]-1) update(root[i],root[i-1],1,n,i,nxt[i-1]-1,b[a[i-1]]);
		else update(root[i],root[i-1],1,n,1,n,0);//区间减
		update(root[i],root[i],1,n,i-1,i-1,LNF);//单点修改
		//注意这个update不能直接在原树上改,要新建一棵树,否则有可能修改到root[i-1]的树
	}
	for(int i=1;i<=n;i++)
		q.push(data(t[root[i]].maxn+t[root[i]].tag,root[i],t[root[i]].where));//先把f[i]的最大值扔进队列
	for(int i=1;i<k;i++)
	{
		data now=q.top();
		q.pop();//取出队首
		update(now.rt,now.rt,1,n,now.id,now.id,LNF);
		q.push(data(t[now.rt].maxn+t[now.rt].tag,now.rt,t[now.rt].where));//放入次大值
	}
	printf("%lld\n",q.top().val);//操作k-1次后直接输出
	return 0;
}
/*
7 3
3 -2 1 2 2 1 3
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值