线段树进阶练习专题

小白逛公园

题目大意:

求一段区间里最大子段和

思路:

一段区间里的最大子段和分为以下几种情况:

1.当最大子段和是左儿子或者右儿子中一个的一部分时:

这段区间里的最大子段和为 左儿子的最大子段和 和 右儿子的最大子段和 的最大值。

2.当最大子段和中既有左儿子的一段又有右儿子的一段时:

容易发现如果出现这种情况,则这个最大值一定是 左儿子的最大后缀 和 右儿子的最大前缀 之和。
所以我们还需要维护每个区间的最大后缀和最大前缀

3.如何维护最大前缀(后缀):

发现最大前缀分为以下几种情况:

  • 左儿子的最大前缀
  • 左儿子的全部和右儿子的最大前缀

两者取最大值,最大后缀同理。

code:

#include<bits/stdc++.h>

using namespace std;

const int MAXN=500100; 

int m,n;
int a[MAXN];

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

struct node{
	int l,r;
	long long sum;//区间和
	long long maxl,maxr;//最大前缀和and最大后缀和
	long long maxn;//最大子段和 
	node()
	{
		l=r=0;
		sum=maxl=maxr=maxn=0;
	}
}z[MAXN*4];

node operator+(const node &l,const node &r)
{
	node res;
	res.l=l.l;res.r=r.r;
	res.sum=l.sum+r.sum;
	res.maxl=max(l.maxl,l.sum+r.maxl);
	res.maxr=max(r.maxr,r.sum+l.maxr);
	res.maxn=max(max(l.maxn,r.maxn),l.maxr+r.maxl);
	return res;
}

void build(int l,int r,int rt)
{
	if(l==r){
		z[rt].l=z[rt].r=l;
		z[rt].sum=a[l];
		z[rt].maxl=z[rt].maxr=z[rt].maxn=a[l];//子段中必须有元素
//		cout<<z[rt].maxr<<endl; 
		return;
	}
	int mid=(l+r)>>1;
//	cout<<mid<<endl;
	build(l,mid,rt<<1);
	build(mid+1,r,rt<<1|1);
	z[rt]=z[rt<<1]+z[rt<<1|1];
}

void update(int l,int r,int rt,int p,int v)//单点修 
{
	if(l==r){
		z[rt].sum=z[rt].maxl=z[rt].maxn=z[rt].maxr=v;
		return;
	}
	int mid=(l+r)>>1;
	if(mid>=p) update(l,mid,rt<<1,p,v);
	else if(mid<p) update(mid+1,r,rt<<1|1,p,v);
	z[rt]=z[rt<<1]+z[rt<<1|1]; 
}

node query(int l,int r,int rt,int nowl,int nowr)
{
	if(l>=nowl&&r<=nowr){
		return z[rt];
	}
	int mid=(l+r)>>1;
	if(mid>=nowl){
		if(mid<nowr) return query(l,mid,rt<<1,nowl,nowr)+query(mid+1,r,rt<<1|1,nowl,nowr);
		else return query(l,mid,rt<<1,nowl,nowr);
	}
	else return query(mid+1,r,rt<<1|1,nowl,nowr);
}

int main()
{
	//cin>>n>>m;
	n=read();m=read();
	for(int i=1;i<=n;i++)
	{
		//cin>>a[i];
		a[i]=read();
	}
	build(1,n,1);
	while(m--)
	{
		int k,p,s;
		k=read();p=read();s=read();
		if(k==1)//选择 
		{
			if(p>s)swap(p,s);
			cout<<query(1,n,1,p,s).maxn<<endl;
		}
		else//单点修 
			update(1,n,1,p,s);
	}
	return 0;
 } 

方差

题目大意:

求一段区间内的数的平均数和方差

思路:

1.求平均数

维护区间和,最后查询求一下平均数

2.求方差

把方差公式展开

所以我们还需要维护区间平方和
so 如何维护区间平方和呢?

字丑见谅qwq

code:

重要的事情说三遍:
\(\huge{\color{red}除n, m, l, r 外,其他输入均为实数!}\)
\(\huge{\color{red}除n, m, l, r 外,其他输入均为实数!!}\)
\(\huge{\color{red}除n, m, l, r 外,其他输入均为实数!!!}\)
(我才不会说我因为这个调了两天)

#include<bits/stdc++.h>

using namespace std;

const int maxn=100100; 

int m,n;
double a[maxn];

struct node{
	int l,r;//左右端点 
	double sum;//区间和 
	double sq_sum;//区间平方和 
	double tag;//加法标记 
	node(){
		l=r=0;
		sum=sq_sum=tag=0;
	}
}z[maxn*4];

node operator+(const node &l,const node &r)
{
	node res;
	res.l=l.l;res.r=r.r;
	res.sum=l.sum+r.sum;
	res.sq_sum=l.sq_sum+r.sq_sum;
	return res;
}

void build(int l,int r,int rt)
{
	if(l==r)
	{
		z[rt].l=z[rt].r=l;
		z[rt].sum=a[l];
		z[rt].sq_sum=a[l]*a[l];
		return;
	}
	int mid=(l+r)>>1;
	build(l,mid,rt<<1);
	build(mid+1,r,rt<<1|1);
	z[rt]=z[rt<<1]+z[rt<<1|1];
}

void color(int rt,double k)//!!!!!
{
	z[rt].sq_sum+=2*k*z[rt].sum+(z[rt].r-z[rt].l+1)*k*k;
    z[rt].sum+=(z[rt].r-z[rt].l+1)*k; 
	z[rt].tag+=k;?
}

void push_col(int rt)
{
	if(z[rt].tag)
	{
		color(rt<<1,z[rt].tag);
		color(rt<<1|1,z[rt].tag);
		z[rt].tag=0;
	}
}

void update(int l,int r,int rt,int nowl,int nowr,double k)
{
	if(l>=nowl&&r<=nowr)
	{
		color(rt,k);
		return; 
	}
	push_col(rt); 
	int mid=(l+r)>>1;
	if(mid>=nowl) update(l,mid,rt<<1,nowl,nowr,k);
	if(mid<nowr) update(mid+1,r,rt<<1|1,nowl,nowr,k);
	z[rt]=z[rt<<1]+z[rt<<1|1];
}

node query(int l,int r,int rt,int nowl,int nowr)//查找区间//!!! 
{
	if(l>=nowl&&r<=nowr)//当前区间包括在内
		return z[rt];
	push_col(rt);
	int mid=(l+r)>>1;
	if(mid>=nowl){
		if(mid<nowr) 
			return query(l,mid,rt<<1,nowl,nowr)+query(mid+1,r,rt<<1|1,nowl,nowr);
		else return query(l,mid,rt<<1,nowl,nowr);
	}
	else return query(mid+1,r,rt<<1|1,nowl,nowr);
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%lf",&a[i]);
	}
	build(1,n,1);
	while(m--)
	{
		int opt,x,y;
		scanf("%d%d%d",&opt,&x,&y);
		if(opt==1){
			double k;
			scanf("%lf",&k);
			update(1,n,1,x,y,k);
		}
		if(opt==2)
		{
			printf("%.4lf\n",query(1,n,1,x,y).sum/(y-x+1));//平均数包没问题的 
		 } 
		if(opt==3){//方差公式 
			double sum1=query(1,n,1,x,y).sum/(double)(y-x+1);//平均数
			double sum2=query(1,n,1,x,y).sq_sum/(double)(y-x+1);//平方和 
			printf("%.4lf\n",(sum2-sum1*sum1));
		}
	}
	return 0;
}

降雨量

听说只有看过天气之子的人才能AC这道题(

题目大意:

询问“X年是自Y年以来降雨量最多的。”这句话是“必真”、“必假”还是“有可能”。

思路:

思路还是很好想的,重要的是判断的细节

  • 先判\(false\)

      1、当右端点年份确定,且中间年份最大降雨量大于等于右端点降雨量

      2、当左端点年份确定,且中间年份最大降雨量大于等于左端点降雨量

      3、当左右端点年份都确定,且左端点降雨量小于等于右端点降雨量

  • 再判\(maybe\):

      1、当左右端点之差不等于左右端点年份之差(等价于年份不连续,也就是我前面所说的更好的判断区间连续的方法)

      2、左端点年份不确定

      3、右端点年份不确定

      (因为已经切掉\(false\)的情况了,那么剩下的情况中可以直接照上面的判断!)

  • 最后判断\(true\):
    若上面情况都不满足,那么肯定是\(true\)

code

#include<bits/stdc++.h>

using namespace std;

const int minn=INT_MIN;
const int maxn=50010;

int m,n;
int pre=0;

struct num{
	int year,v;
	bool vis;
	num(){year=0;v=minn;vis=false;}
}a[100010]; 

struct node{
	int maxv,minv;
	node()
	{
		maxv=minv=minn;
	}
}z[400010];

node operator+(const node &l,const node &r)
{
	node res;
	res.maxv=max(l.maxv,r.maxv);
	res.minv=min(l.minv,r.minv);
	return res;
}

int go(int x)//最大的year小于等于x的位置 
{
	
    if(x<a[1].year)return 0;
	//找到x在离散化后的数
	int l=1,r=2*n;
	while(l<r)
	{
		int mid=(l+r)>>1;
		if(a[mid].year<=x) l=mid+1;
		else r=mid;
	}
	return l-1;
}

void build(int l,int r,int rt)
{
	if(l==r){
		z[rt].maxv=z[rt].minv=a[l].v;
//		cout<<rt<<" "<<l<<" "<<r<<" "<<z[rt].maxv<<" "<<z[rt].minv<<endl; 
		return;
	}
	int mid=(l+r)>>1;
	build(l,mid,rt<<1);
	build(mid+1,r,rt<<1|1);
	z[rt]=z[rt<<1]+z[rt<<1|1];
//	cout<<rt<<" "<<l<<" "<<r<<" "<<z[rt].maxv<<" "<<z[rt].minv<<endl; 
 } 
 
 node query(int l,int r,int rt,int nowl,int nowr)
 {
 	if(l>=nowl&&r<=nowr)
 	{
 		return z[rt];
	 }
	int mid=(l+r)>>1;
	if(mid>=nowl)
	{
		if(mid<nowr)
			return query(l,mid,rt<<1,nowl,nowr)+query(mid+1,r,rt<<1|1,nowl,nowr);
		else return query(l,mid,rt<<1,nowl,nowr);
	}
	else return query(mid+1,r,rt<<1|1,nowl,nowr);
 }

int main()
{
//	freopen("P2471_1.in","r",stdin);
//	freopen("P2471_1.out","w",stdout);
	cin>>n;
	int now=0;
	for(int i=1;i<=n;i++)
	{
		int year,v;
		cin>>year>>v;
		if(now+1!=year&&i!=1)//year和上一个之间还有年份
		{
			pre++;
		}
		now=year;	
		pre++;
		a[pre].v=v;
		a[pre].year=year;
		a[pre].vis=true;
	}
	for(int i=1;i<=n*2;i++)
	{
		if(!a[i].vis)//实际并没有这个节点
			a[i].year=a[i-1].year+1; //离散化成功! 
	}
	build(1,n*2,1);
	cin>>m;
	while(m--)
	{
		int x,y;
		cin>>y>>x;
		int yy=go(y);
		int xx=go(x);
	//	cout<<xx<<" "<<yy<<endl;
		node res;
		if(xx==yy){
			cout<<"maybe"<<endl;
			continue;
		}
		if(a[xx].v!=minn&&a[yy].v!=minn&&x==y+1&&a[xx].v<a[yy].v)
		{
			cout<<"true"<<endl;
			continue;
		}
		if(a[xx].v!=minn&&a[yy].v!=minn&&x==y+1&&a[xx].v>=a[yy].v)
		{
			cout<<"false"<<endl;
			continue;
		}
		if(xx==yy+1&&x!=y+1)
		{
			cout<<"maybe"<<endl;
			continue;
		  }  
		if(yy+1<=xx-1) res=query(1,n*2,1,yy+1,xx-1);
		if(xx==yy){
			cout<<"maybe"<<endl;
			continue;
		}
		if((a[xx].v!=minn&&res.maxv>=a[xx].v)||(a[yy].v!=minn&&res.maxv>=a[yy].v)||(a[xx].v!=minn&&a[yy].v!=minn&&a[yy].v<=a[xx].v))
		{
			cout<<"false"<<endl;
			continue;
		}
		else if((yy+1<=xx-1&&res.minv==minn)||a[xx].v==minn||a[yy].v==minn)
		{
			cout<<"maybe"<<endl;
			continue;
		}
		else cout<<"true"<<endl;
	}
	return 0;
 } 

后记:

posted @ 2025-01-05 22:31  lazy_ZJY  阅读(34)  评论(0)    收藏  举报