bzoj4504 K个串 可持久化线段树+堆

14 篇文章 0 订阅

Description


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

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

Solution


非常套路的套路题

我们开n棵线段树,第i棵的第j个位置代表右端点为i,左端点为j时的答案,每次区间更改贡献求最大值
然后开一个堆记录最大值和最大值对应的区间。我们每次取出最大值然后把可行区间裂成两半再塞回去,做k次就没了

使用标记永久化可以更加精简和省内存

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <queue>
#include <map>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)
#define fi first
#define se second

typedef long long LL;
typedef std:: pair <LL,int> pair;
const LL INF=1e15;
const int N=200005;

struct treeNode {int l,r; LL tag; pair max;} t[N*51];
struct data {
	int i,l,r; pair max;

	bool operator <(data b) const {
		return max<b.max;
	}
} ;

std:: map <int,int> last;
std:: priority_queue <data> heap;

int pre[N],root[N],a[N],tot;

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

void modify(int &now,int pre,int tl,int tr,int l,int r,int v) {
	if (r<l) return ;
	t[now=++tot]=t[pre];
	if (tl>=l&&tr<=r) {
		t[now].tag+=v;
		t[now].max.fi+=v;
		return ;
	}
	int mid=(tl+tr)>>1;
	modify(t[now].l,t[pre].l,tl,mid,l,std:: min(r,mid),v);
	modify(t[now].r,t[pre].r,mid+1,tr,std:: max(mid+1,l),r,v);
	pair qx=t[t[now].l].max; qx.fi+=t[now].tag;
	pair qy=t[t[now].r].max; qy.fi+=t[now].tag;
	t[now].max=std:: max(qx,qy);
}

pair query(int now,int tl,int tr,int l,int r) {
	if (r<l) return pair(-INF,0);
	if (tl>=l&&tr<=r) return t[now].max;
	int mid=(tl+tr)>>1;
	pair qx=query(t[now].l,tl,mid,l,std:: min(r,mid)); qx.fi+=t[now].tag;
	pair qy=query(t[now].r,mid+1,tr,std:: max(mid+1,l),r); qy.fi+=t[now].tag;
	return std:: max(qx,qy);
}

void build_tree(int &now,int tl,int tr) {
	t[now=++tot].max=pair(0,tl);
	if (tl==tr) return ;
	int mid=(tl+tr)>>1;
	build_tree(t[now].l,tl,mid);
	build_tree(t[now].r,mid+1,tr);
}

int main(void) { t[0].max=pair(-INF,0);
	freopen("data.in","r",stdin);
	freopen("myp.out","w",stdout);
	int n=read(),m=read();
	build_tree(root[0],1,n);
	rep(i,1,n) {
		a[i]=read();
		pre[i]=last[a[i]];
		last[a[i]]=i;
		modify(root[i],root[i-1],1,n,pre[i]+1,i,a[i]);
		pair wjp=query(root[i],1,n,1,i);
		heap.push((data) {i,1,i,query(root[i],1,n,1,i)});
	}
	LL ans=0;
	for (pair max,wjp;m--;) {
		data top=heap.top(); heap.pop();
		int i=top.i; max=top.max; ans=max.fi;
		if (max.se+1<=top.r) {
			wjp=query(root[i],1,n,max.se+1,top.r);
			heap.push((data) {i,max.se+1,top.r,wjp});
		}
		if (top.l<=max.se-1) {
			wjp=query(root[i],1,n,top.l,max.se-1);
			heap.push((data) {i,top.l,max.se-1,wjp});
		}
	}
	printf("%lld\n", ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值