【贪心+堆+ST算法】BZOJ 4458: GTY的OJ 题解

写在前面

失踪人口在8个月的文化课狂补和4天的适(tui)应(fei)后,应该算正式回归了。(目前为半失踪人口)

本来想换个Blog当作重新开始,但反正都退役了,就不瞎折腾了…

BZOJ 4458: GTY的OJ

https://www.lydsy.com/JudgeOnline/problem.php?id=4458

题目概述

给出一棵带有点权的 n n n个节点的树,在树上选出 m m m个长度在 L L L R R R之间的路径,求这 m m m条路径的权值最大值。

n , m ≤ 1 0 5 n,m\le 10^5 n,m105

解题分析

暴力肯定不可取,所以需要换个思路。

等等,这好像有一道题

BZOJ 2006: [NOI2010]超级钢琴

https://www.lydsy.com/JudgeOnline/problem.php?id=2006

好像是上面那题的序列版,处理序列肯定比在树上容易,所以先解决这个。

假设我们抓到一个点 i i i,那么这个点所形成的区间的起点就在 [ i − R + 1 , i − L + 1 ] [i-R+1,i-L+1] [iR+1,iL+1]内,权值是多少?通过构造前缀和就发现, s u m [ i ] − s u m [ s ] sum[i]-sum[s] sum[i]sum[s]

然后题目又要求前 m m m大,所以想到了堆,将所有的序列放在堆内,取出前 m m m个最大的加起来就是答案。但是区间这么多,所以还需要进行优化。

对于一个序列

无标题1

假设现在我们已经找到了终点为i的最大序列,起点为t,那么现在把它取出算入答案,为了不算重,下一次我们将可覆盖的范围拆为两段,然后在这之间寻找最大值,加入堆中。

那么这样堆中最多只有 n + m n+m n+m个元素,问题就剩下了一个:如何求最大值?

再看看这个式子 s u m [ i ] − s u m [ s ] sum[i]-sum[s] sum[i]sum[s],其中 s u m [ i ] sum[i] sum[i]是固定的,所以就要求 s u m [ s ] sum[s] sum[s]最小,在一个特定要求的范围内找最小值,什么,RMQ啊!

总结一下,求出 s u m sum sum数组后RMQ处理,初始将对每个位置为终点进行处理,寻找最大的区间并放入堆中。询问时找出最大值的区间 [ p o s , i ] [pos,i] [pos,i],那么将 p o s pos pos两端分别当作两个新的范围处理。

代码如下

#include<cmath>
#include<cstdio>
#include<algorithm>
#define maxn 500005
#define LL long long
using namespace std;
int n,k,L,R,len,f[maxn][22];
LL sum[maxn],ans;
inline void readi(int &x){
	x=0; char ch=getchar(),lst='+';
	while ('0'>ch||ch>'9') {lst=ch; ch=getchar();}
	while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
	if (lst=='-') x=-x;
}
int Rmin(int i,int j){return sum[i-1]>sum[j-1]?j:i;}
int getR(int L,int R){int j=log2(R-L+1); return Rmin(f[L][j],f[R-(1<<j)+1][j]);}
void makeR(){
	for (int j=1,to=log2(n);j<=to;j++)
		for (int i=1;i<=n+1-(1<<j);i++)
			f[i][j]=Rmin(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
void _init(){
	freopen("piano.in","r",stdin);
	freopen("piano.out","w",stdout);
	readi(n); readi(k); readi(L); readi(R); sum[0]=0;
	for (int i=1,x;i<=n;i++) {readi(x); sum[i]=sum[i-1]+x; f[i][0]=i;}
	makeR();
}
struct data{
	int id,L,R,t; LL tem;
	data (int id=0,int L=0,int R=0):id(id),L(L),R(R) {t=getR(L,R); tem=sum[id]-sum[t-1];}
	bool operator < (const data b)const{
		return tem<b.tem;
	}
}hep[maxn<<1];
void putH(data a){
	hep[++len]=a;
	for (int son=len;son!=1&&hep[son>>1]<hep[son];son>>=1) swap(hep[son],hep[son>>1]);
}
data getH(){
	data ans=hep[1]; hep[1]=hep[len--]; int fa=1,son;
	while ((fa<<1)<=len){
		son=fa<<1;
		if (son<len&&hep[son]<hep[son+1]) son++;
		if (hep[fa]<hep[son]) {swap(hep[fa],hep[son]); fa=son;}
		else break;
	}
	return ans;
}
void _solve(){
	len=ans=0;
	for (int i=L;i<=n;i++) putH(data(i,max(i-R+1,1),i-L+1));
	for (int i=1;i<=k;i++){
		data te=getH(); ans+=te.tem;
		if (te.L<te.t) putH(data(te.id,te.L,te.t-1));
		if (te.t<te.R) putH(data(te.id,te.t+1,te.R));
	}
	printf("%lld",ans);
}
int main()
{
	_init();
	_solve();
	return 0;
}

然后再说这题。树上的话有许多细节,但运用倍增的思想结合超级钢琴的思路也就好了。

顺便说一句,手写堆比STL快…

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxn 500005
#define LL long long
using namespace std;
int n,m,L,R,len,tot,stp[maxn],fa[maxn][22],f[maxn][22];
LL ans,sum[maxn];
inline void readi(int &x){
	x=0; char ch=getchar(),lst='+';
	while ('0'>ch||ch>'9') {lst=ch; ch=getchar();}
	while ('0'<=ch&&ch<='9') {x=x*10+ch-'0'; ch=getchar();}
	if (lst=='-') x=-x;
}
int minR(int i,int j){return sum[fa[i][0]]<sum[fa[j][0]]?i:j;}
int getF(int x,int dep){
	for (int j=tot;j>=0;j--)
		if (dep&(1<<j)) x=fa[x][j];
	return x;
}
int askR(int L,int R){
	int tem=f[L][0];
	for (int j=tot;j>=0;j--)
		if (stp[fa[R][j]]>=stp[L]) {tem=minR(tem,f[R][j]); R=fa[R][j];}
	return tem;
}
void makeR(){
	for (int j=1;j<=tot;j++)
		for (int i=1;i<=n;i++)
			fa[i][j]=fa[fa[i][j-1]][j-1];
	for (int j=1;j<=tot;j++)
		for (int i=1;i<=n;i++)
			if (stp[i]>=(1<<j)) f[i][j]=minR(f[i][j-1],f[fa[i][j-1]][j-1]);
}
void _init(){
	freopen("oj.in","r",stdin);
	freopen("oj.out","w",stdout);
	readi(n); tot=0;
	for (int i=1;i<=n;i++) readi(fa[i][0]);
	for (int i=1,x;i<=n;i++){
		readi(x); sum[i]=sum[fa[i][0]]+x;
		stp[i]=stp[fa[i][0]]+1; f[i][0]=i; tot=max(tot,stp[i]);
	}
	tot=log2(tot); readi(m); readi(L); readi(R); makeR();
}
struct data{
	int id,L,R,t; LL num;
	data (int id=0,int L=0,int R=0):id(id),L(L),R(R){
		t=askR(L,R); num=sum[id]-sum[fa[t][0]];
	}
	bool operator < (const data b)const{
		return num<b.num;
	}
}hep[maxn<<1];
void putH(data a){
	hep[++len]=a;
	for (int son=len;son!=1&&hep[son>>1]<hep[son];son>>=1)
		swap(hep[son],hep[son>>1]);
}
data getH(){
	data ans=hep[1]; hep[1]=hep[len--];
	for (int fa=1,son;(fa<<1)<=len;fa=son){
		son=fa<<1;
		if (son<len&&hep[son]<hep[son+1]) son++;
		if (hep[fa]<hep[son]) swap(hep[fa],hep[son]); else break;
	}
	return ans;
}
void _solve(){
	ans=len=0;
	for (int i=1;i<=n;i++)
		if (stp[i]>=L){
			int l=max(getF(i,R-1),1),r=getF(i,L-1);
			putH(data(i,l,r));
		}
	for (int i=1;i<=m;i++){
		data a=getH(); ans+=a.num;
		if (a.t!=a.L){
			int Fl=fa[a.t][0];
			putH(data(a.id,a.L,Fl));
		}
		if (a.t!=a.R){
			int le=stp[a.id]-stp[a.t],Fr=getF(a.id,le-1);
			putH(data(a.id,Fr,a.R));
		}
	}
	printf("%lld",ans);
}
int main()
{
	_init();
	_solve();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值