首页 > 其他分享 >【BZOJ4504】K个串(优先队列,可持久化线段树)

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

时间:2022-10-28 20:01:52浏览次数:67  
标签:nxt ch BZOJ4504 10 队列 线段 int maxn rc

Description

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

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

Input

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

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

Output

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

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

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

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

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

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

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

设 \(nxt_i\) 表示数 \(a_i\) 在 \(i\) 之后出现的第一个位置,显然可以 \(O(n)\) 预处理。

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

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

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

  2. 当 \(2\leq j < nxt_1\) 时,如下图:

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

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

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

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

综上所述,可以推出:

当 \(j< i-1\),\(f(i,j)=f(i-1,j)=-\infty\);

当 \(j=i-1\) 时,\(f(i,j)=-\infty\);

当 \(i \leq j < nxt_{i-1}\) 时,\(f(i,j)=f(i-1,j)-a_{i-1}\);

当 \(j\geq nxt_{i-1}\) 时,\(f(i,j)=f(i-1,j)\)。

我们就可以发现,\(f(i-1,j)\) 和 \(f(i,j)\) 有一段区间是一样的(\(j<i-1\) 或 \(j\geq nxt_{i-1}\)),有一段区间是 \(f(i,j)\) 等于 \(f(i-1,j)\) 减去一个相同的值(\(i \leq j < nxt_{i-1}\)),有一个特殊的位置是 \(f(i,j)=-\infty\)(\(j=i-1\))。

容易联想到用可持久化线段树维护 \(f(i,j)\)。(\(n\) 棵线段树,每棵线段树 \(i\) 存储 \(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
*/

标签:nxt,ch,BZOJ4504,10,队列,线段,int,maxn,rc
From: https://www.cnblogs.com/ez-lcw/p/16837304.html

相关文章