首页 > 其他分享 >山海经(线段树)题解

山海经(线段树)题解

时间:2024-02-22 20:01:26浏览次数:30  
标签:山海经 题解 线段 mx0 mx1 lft iht str mx

原题链接:COGS 775
题目描述:
“南山之首曰鹊山。其首曰招摇之山,临于西海之上,多桂,多金玉。有草焉,其状如韭而青华,其名曰祝余,食之不饥……又东三百里,曰堂庭之山,多棪木,多白猿,多水玉,多黄金。又东三百八十里,曰猨翼之山,其中多怪兽,水多怪鱼,多白玉,多蝮虫,多怪蛇,名怪木,不可以上。……”(其实就是一堆废话)
《山海经》是以山为纲,以海为线记载古代的河流、植物、动物及矿产等情况,而且每一条记录路线都不会有重复的山出现。某天,你的地理老师想重游《山海经》中的路线,为了简化问题,老师已经把每座山用一个整数表示他对该山的喜恶程度,他想知道第a座山到第b座山的中间某段路(i,j)能使他感到最满意,即(i,j)这条路上所有山的喜恶度之和是(c,d)(a<=c<=d<=b)最大值。于是老师便向你请教,你能帮助他吗?(我不能)
值得注意的是,在《山海经》中,第i座山只能到达第i+1座山。(好奇怪的规定)
输入格式:
输入第1行是两个数n,m,表示一共有n座山,m表示老师想查询的数目。
第2行是n个整数,代表n座山的喜恶度,绝对值均小于10000。
以下m行每行有a,b两个数,1<=a<=b<=m,表示第a座山到第b座山。
输出格式:
一共有m行,每行有3个数i,j,s,表示从第i座山到第j座山总的喜恶度为s。
显然,对于每个查询,有a<=i<=j<=b,如果有多组解,则输出i最小的,如果i也相等,则输出j最小的解。
思路:
首先,这道题在审题方面就是一个大坎。如果我们设一个变量mx[i][j]代表由i到j的所有山喜恶度的和,那么我们要求的ans[c][d]则是在众多mx[i]j中再找出一个最大值。如果我们就这么打,每一次查询的时间复杂度是O(ab),极限数据下单次也会超时,更不用说多组测试数据了。
那么,既然它维护的是区间特殊值,我们就可以想到用线段树求解。我们要维护的是区间上所有子区间和的最大值,所以我们既要维护一个区间的区间和,还要维护答案。我们分析一下,如果把一个区间分为两个子区间,那么最大区间和的位置有几种情况?

很明显,只有这三种。对于前两种来说,只需把左右子树的mx值取一个最大,但是第三种就相当难办。思考一下,这种情况就相当于两个区间拼起来,而这两个区间,一个靠左一个靠右。我们如果想让它们拼接起来的区间最优,就要让它们在满足可以拼接的位置条件下尽可能更优。于是,我定义了mx0和mx1,分别代表靠左区间的最优值和靠右区间的最优值。那mx0和mx1如何由它的子区间推知呢?我们先假设它的两个子区间的mx0,mx1都已经确定,那么左区间的mx0绝对是一种情况,还有就是左区间的总和sm加上右区间的mx0。同理,mx1也应考虑右区间mx0与右区间mx1加左区间mx1。由于mx不考虑位置问题,那么mx绝对不小于mx0和mx1。这样就可以完成这道题的主体部分。至于输出路径,只需在每次更新mx时更新一下就好了。
代码:

#include<bits/stdc++.h>
using namespace std;
struct node
{
	int begin,end;//原区间的位置 
	int sm;//区间和 
	int mx,l,r;//总的最大值及其位置 
	int mx0,l0,r0;//靠左的最大值及其位置
	int mx1,l1,r1;//靠右的最大值及其位置
}str[400004];//线段树 
int a,b,c[100001],u,v;
inline int lft(int x)
{
	return x<<1;//找左区间编号 
}
inline int iht(int x)
{
	return x<<1|1;//找右区间编号 
}
void makstr(int x,int m,int n)//建树 
{
	if(m>n)
	{
		return;
	}
	str[x].begin=m;
	str[x].end=n;
	if(m==n)
	{
		str[x].mx=c[m];
		str[x].mx0=c[m];
		str[x].mx1=c[m];
		str[x].sm=c[m];
		str[x].l0=m;
		str[x].l1=m;
		str[x].l=m;
		str[x].r0=n;
		str[x].r1=n;
		str[x].r=n;
		return;//对单点区间的操作
	}
	else
	{
		int h=(m+n)>>1;//下面递归 
		makstr(lft(x),m,h);
		makstr(iht(x),h+1,n);
		str[x].sm=str[lft(x)].sm+str[iht(x)].sm;//求和 
		if(str[lft(x)].mx>=str[iht(x)].mx)//先把两个子区间比较一下 
		{
			str[x].mx=str[lft(x)].mx;
			str[x].l=str[lft(x)].l;
			str[x].r=str[lft(x)].r;
			//mx相等时很明显左区间的左右边界都更小 
		}
		else
		{
			str[x].mx=str[iht(x)].mx;
			str[x].l=str[iht(x)].l;
			str[x].r=str[iht(x)].r;
		}
		str[x].l0=str[x].begin;//固定的边界 
		if(str[lft(x)].mx0>=str[lft(x)].sm+str[iht(x)].mx0)//找它的mx0 
		{
			str[x].mx0=str[lft(x)].mx0;
			str[x].r0=str[lft(x)].r0;
			//mx0相等时很明显不包含右区间的情况右边界更小 
		}
		else
		{
			str[x].mx0=str[lft(x)].sm+str[iht(x)].mx0;
			str[x].r0=str[iht(x)].r0;
		}
		str[x].r1=str[x].end;//固定的边界
		if(str[iht(x)].mx1>str[iht(x)].sm+str[lft(x)].mx1)//找它的mx1
		{
			str[x].mx1=str[iht(x)].mx1;
			str[x].l1=str[iht(x)].l1;
		}
		else
		{
			str[x].mx1=str[iht(x)].sm+str[lft(x)].mx1;
			str[x].l1=str[lft(x)].l1;
			//mx1相等时很明显包含左区间的情况左边界更小
		}
		if(str[x].mx<str[lft(x)].mx1+str[iht(x)].mx0)//找第三种情况 
		{
			str[x].mx=str[lft(x)].mx1+str[iht(x)].mx0;
			str[x].l=str[lft(x)].l1;
			str[x].r=str[iht(x)].r0;
		}
		else
		{
			if(str[x].mx==str[lft(x)].mx1+str[iht(x)].mx0)//判断是否修改位置 
			{
				if(str[x].l>str[lft(x)].l1)
				{
					str[x].l=str[lft(x)].l1;
					str[x].r=str[iht(x)].r0;
				}
				else
				{
					if(str[x].l==str[lft(x)].l1&&str[x].r>str[iht(x)].r0)
					{
						str[x].r=str[iht(x)].r0;
					}
				}
			}
		}
	}
}
node foudstr(int x,int m,int n)//找值 
{
	if(m==str[x].begin&&n==str[x].end)//递归边界 
	{
		return str[x];
	}
	else
	{
		int h=(str[x].begin+str[x].end)>>1;//下面是递归 
		if(n<=h)
		{
			return foudstr(lft(x),m,n);
		}
		if(m>h)
		{
			return foudstr(iht(x),m,n);
		}
		node j=foudstr(lft(x),m,h),k=foudstr(iht(x),h+1,n),o;
		//以下一部分和上面很相似 
		o.begin=m;
		o.end=n;
		if(j.mx>k.mx)
		{
			o.mx=j.mx;
			o.l=j.l;
			o.r=j.r;
		}
		else
		{
			if(j.mx==k.mx)
			{
				o.mx=j.mx;
				o.l=j.l;
				o.r=j.r;
			}
			else
			{
				o.mx=k.mx;
				o.l=k.l;
				o.r=k.r;
			}
		}
		o.l0=o.begin;
		if(j.mx0>j.sm+k.mx0)
		{
			o.mx0=j.mx0;
			o.r0=j.r0;
		}
		else
		{
			if(j.mx0==j.sm+k.mx0)
			{
				o.mx0=j.mx0;
				o.r0=j.r0;
			}
			else
			{
				o.mx0=j.sm+k.mx0;
				o.r0=k.r0;
			}
		}
		o.r1=o.end;
		if(k.mx1>k.sm+j.mx1)
		{
			o.mx1=k.mx1;
			o.l1=k.l1;
		}
		else
		{
			if(k.mx1==k.sm+j.mx1)
			{
				o.mx1=k.sm+j.mx1;
				o.l1=j.l1;
			}
			else
			{
				o.mx1=k.sm+j.mx1;
				o.l1=j.l1;
			}
		}
		if(o.mx<j.mx1+k.mx0)
		{
			o.mx=j.mx1+k.mx0;
			o.l=j.l1;
			o.r=k.r0;
		}
		else
		{
			if(o.mx==j.mx1+k.mx0)
			{
				if(o.l>j.l1)
				{
					o.l=j.l1;
					o.r=k.r0;
				}
				else
				{
					if(o.l==j.l1)
					{
						if(o.r>k.r0)
						{
							o.r=k.r0;
						}
					}
				}
			}
		}
		return o;
	}
}
int main()
{
	scanf("%d%d",&a,&b);
	for(int i=1;i<=a;i++)
	{
		scanf("%d",&c[i]);
	}
	makstr(1,1,a);
	for(int i=1;i<=b;i++)
	{
		scanf("%d%d",&u,&v);
		node nd=foudstr(1,u,v);
		printf("%d %d %d\n",nd.l,nd.r,nd.mx);
	}
	return 0;
}

总结:
这道题当时我打了一天,主要是因为维护的12个变量是真的想不到。针对最大区间和的情况也想了很久。尤其是最后mx0和mx1不能和mx再进行比较。但是,打完了这道题以后再反过来从理论层面分析一下,发现也并不是非常难。思路通了以后,代码复杂度再高也只需Ctrl+V。

标签:山海经,题解,线段,mx0,mx1,lft,iht,str,mx
From: https://www.cnblogs.com/ywhhdjser-97/p/18024159

相关文章

  • 线段树模板
    向上回溯voidpushup(intrt){ t[rt].sum+=t[lc].sum+t[rc].sum; t[rt].mx=max(t[lc].mx,t[rc].mx);}建树voidbuild(intrt,intl,intr){ t[rt].l=l; t[rt].r=r; if(l==r){ t[rt].mx=t[rt].sum=a[l]; return; } intmid=(l+r)>>1......
  • 玉蟾宫 题解
    题目描述有一天,小猫rainbow和freda来到了湘西张家界的天门山玉蟾宫,玉蟾宫宫主蓝兔盛情地款待了它们,并赐予它们一片土地。这片土地被分成N*M个格子,每个格子里写着’R’或者’F’,R代表这块土地被赐予了rainbow,F代表这块土地被赐予了freda。现在freda要在这里卖萌。。。它要找一块......
  • 洛谷题单指南-贪心-P1803 凌乱的yyy / 线段覆盖
    原题链接:https://www.luogu.com.cn/problem/P1803题意解读:通过某种贪心策略,使得能参加的比赛数越多越好。解题思路:将比赛按照结束时间由小到大哦排序,贪心策略是优先选择结束时间早的比赛,因为这样能保证后面参加更多其他比赛100分代码:#include<bits/stdc++.h>usingnamespa......
  • P6466 分散层叠算法(Fractional Cascading) 题解
    题目链接:分散层叠算法比较妙的东西,在很多涉及到若干个有序块的\(kth\)查询的ynoi题中都有妙用。这里简单提提。两种暴力解法在其他文章已有涉及,在此不再赘述。讲讲具有该怎么写这个算法,首先我们需要预处理出新的\(k\)个序列,不妨记每个为\(M_i\)。\(M_{n}=L_n\),其中\(L\)......
  • [ABC259Ex] Yet Another Path Counting 题解
    Description有\(N\)行\(N\)列的网格图,只能向下或向右走,合法路径的开端和结尾的格子上数字一样找到合法路径条数,对\(998244353\)取模\(1\leqN\leq400,1\leqa_{i,j}\leqN^2\)。Solution有一个\(O(n^4)\)的做法是每次枚举起点和终点然后用组合数计算答案,但是由于同......
  • 山海经&&Atcoder Alternating String (线段树)
    前言:为什么把他们放在一起?因为我发现把pushup向上回溯放结构体类型函数里比较方便并且这两题确实也有相同思想山海经这题分三种情况左子树前缀和+右子树前缀和2.右子树后缀和与右总区间+左子树3.左区间最大子段与右区间最大子段与左后缀与右前缀特别要注意......
  • 牛吃草 题解
    牛吃草居然真的是牛吃草Description由于现代化进程的加快,农场的养殖业也趋向机械化。lyz决定购置若干台自动喂草机来减少自己每天的工作量。为了简化问题,lyz决定将草地建模成一条线段,总长为\(n\),即共有\(n\)个单位长度,编号从左至右为\(1∼n\)。lyz可以在每个单位长度独......
  • blocks 单调栈、单调队列题解
    blocks题解:1、题面:2、分析:题意大概就是说,找一段最长的区间,并且这段区间的平均值>=k,那么我们可以对他的每一个值减去k,最终求和>=0即可。那我们需要对每个可能的左端点和右端点进行考虑,并以此让他们进行配对,看他们之间的区间和是否非负。那么我们先定住一个右端点,再依次考虑......
  • 理想的正方形 题解
    题目描述有一个a*b的整数组成的矩阵,现请你从中找出一个n*n的正方形区域,使得该区域所有数中的最大值和最小值的差最小。输入格式第一行为3个整数,分别表示a,b,n的值第二行至第a+1行每行为b个非负整数,表示矩阵中相应位置上的数。每行相邻两数之间用一空格分隔。100%的数据2<......
  • P5344 【XR-1】逛森林 题解
    题目链接:逛森林很早就想写写倍增优化建图,尤其是这题,奈何之前知识点没点够,本题线段树优化建图要优一些,不再赘述,没注意\(m\)是\(1e6\),挂了\(n\)多发才发现。后续再详细讲解倍增优化建图,这里简述本题做法。倍增优化建图其实和线段树优化建图恰不多的思想,为倍增求\(LCA\)的每......