GSS7 - Can you answer these queries VII 题解

题目传送门

题目大意: 求树上最大子段和,带修。

如果做了gss系列的前六题,那么这题相信不难。用一个树链剖分维护这棵树即可。

但是细节比较烦。

具体做法:对于求 x x x y y y路径上的最大子段和,只需要让 x x x y y y往上跑一遍,将经过的链的信息合并在一起即可,但是最后当 x x x y y y在一条重链上时,我们需要将 x x x经过的链的信息和 y y y经过的链的信息合并,而此时这两个信息的左端点都朝上,于是可以将其中一个翻转,于是就可以接起来了。

具体细节看代码,会有很清楚的注释 (毕竟自己都觉得上面这个讲的不明白)

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 100010

struct edge{int y,next;};
edge e[maxn*2];
int first[maxn];
int n,m,a[maxn];
void buildroad(int x,int y)
{
	static int len=0;
	e[++len]=(edge){y,first[x]};
	first[x]=len;
}
int fa[maxn],size[maxn],mson[maxn],deep[maxn];
void dfs1(int x)//树剖预处理,找到重儿子,求出深度之类的
{
	size[x]=1;
	for(int i=first[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(y==fa[x])continue;
		fa[y]=x;
		deep[y]=deep[x]+1;
		dfs1(y);
		if(size[y]>size[mson[x]])mson[x]=y;
		size[x]+=size[y];
	}
}
int now[maxn],old[maxn],top[maxn],tot=0;
void dfs2(int x,int tt)//树剖预处理,给每个点重新编号
{
	now[x]=++tot;
	old[tot]=x;
	top[x]=tt;
	if(mson[x]!=0)dfs2(mson[x],tt);
	for(int i=first[x];i;i=e[i].next)
	if(e[i].y!=fa[x]&&e[i].y!=mson[x])dfs2(e[i].y,e[i].y);
}
struct node{
	int l,r,sl,sr,ans,sum,lazy;
	node *zuo,*you;
	void check(node *le,node *ri)//表示将两个点的信息合并到自己身上
	{
		if(le==NULL||ri==NULL)//如果只有一个点,就直接继承
		{
			if(le==NULL)sum=ri->sum,sl=ri->sl,sr=ri->sr,ans=ri->ans;
			else sum=le->sum,sl=le->sl,sr=le->sr,ans=le->ans;
			return;
		}
		sum=le->sum+ri->sum;
		sl=max(le->sl,le->sum+ri->sl);
		sr=max(ri->sr,ri->sum+le->sr);
		ans=max(le->ans,max(ri->ans,le->sr+ri->sl));
	}
	node(int x,int y)//建树
	{
		if(x==0)return;
		l=x,r=y;lazy=-23333;
		//注意lazy不能为0,因为修改操作是可以为0的,在没有修改操作时lazy的绝对值要大于10000保证不和修改操作冲突
		if(l<r)
		{
			int mid=l+r>>1;
			zuo=new node(x,mid);
			you=new node(mid+1,y);
			check(zuo,you);
		}
		else sum=sl=sr=ans=a[old[x]],zuo=NULL,you=NULL;
	}
	node *get(int x,int y)//求x~y的最大子段和
	{
		pushdown();
		if(l==x&&r==y)//如果完全符合,注意不能直接返回自己,要新建一个点继承自己的信息然后返回这个点
		{//因为在ask函数中会有将点的信息翻转的操作,如果直接返回自己可能会把自己的信息搞乱
		//顺便扯一句,就是因为这个WA了半天
			node *re=new node(0,0);//注意不是re=this,不然的话跟返回this没有区别
			re->check(this,NULL);//继承信息
			return re;
		}
		int mid=l+r>>1;
		if(y<=mid)return zuo->get(x,y);
		else if(x>=mid+1)return you->get(x,y);
		else
		{
			node *left=zuo->get(x,mid),*right=you->get(mid+1,y);
			node *re=new node(0,0);
			re->check(left,right);
			return re;
		}
	}
	void pushdown()
	{
		if(lazy!=-23333)
		{
			if(lazy>0)sl=sr=sum=ans=(r-l+1)*lazy;
			else sl=sr=ans=lazy,sum=(r-l+1)*lazy;
			if(zuo!=NULL)zuo->lazy=lazy,you->lazy=lazy;
			lazy=-23333;
		}
	}
	void change(int x,int y,int z)//将x~y的值改成z
	{
		if(l==x&&r==y)
		{
			lazy=z;
			pushdown();
			return;
		}
		pushdown();
		int mid=l+r>>1;
		if(y<=mid)zuo->change(x,y,z),you->pushdown();
		else if(x>=mid+1)you->change(x,y,z),zuo->pushdown();
		else zuo->change(x,mid,z),you->change(mid+1,y,z);
		check(zuo,you);
	}
};
node *root;
void ask(int x,int y)//重头戏在此
{
	node *l=NULL,*r=NULL;//*l,*r用来存x和y往上跑时一路上的信息
	bool v=false;//v记录x和y时交换过的还是没交换过的,结合代码感性理解(虽然不知道有没有用)
	while(top[x]!=top[y])
	{
		if(deep[top[x]]>deep[top[y]])swap(x,y),swap(l,r),v^=1;//注意这里l和r也要交换
		node *wula=root->get(now[top[y]],now[y]);//记录下当前点到重链顶端的答案
		if(r==NULL)r=wula;//如果之前没有信息,就直接存
		else
		{
			node *wulala=new node(0,0);
			wulala->check(wula,r);//否则将以前的信息和现在的信息合并,注意,这里面一定是(wula,r),而不能是(r,wula)
			r=wulala;
		}
		y=fa[top[y]];
	}
	if(deep[x]>deep[y])swap(x,y),swap(l,r),v^=1;
	node *wula=root->get(now[x],now[y]);//当x和y在同一条链上时,最后求出x到y的最大子段和
	if(v)swap(l,r),swap(wula->sl,wula->sr);//最后要让l的右端点对着wula的左端点,wula的右端点对着r的左端点
	//这样才能正常的合并
	if(l!=NULL)swap(l->sl,l->sr);//注意记得判l是否为NULL
	node *wulala=new node(0,0);//最后将三者的信息合并
	wulala->check(l,wula);
	wula->check(wulala,r);
	if(wula->ans>=0)printf("%d\n",wula->ans);//如果小于0,不如不选
	else printf("0\n");
}
void change(int x,int y,int z)
{
	while(top[x]!=top[y])//这个很简单,跑一遍沿路修改即可
	{
		if(deep[top[x]]>deep[top[y]])swap(x,y);
		root->change(now[top[y]],now[y],z);
		y=fa[top[y]];
	}
	if(deep[x]>deep[y])swap(x,y);
	root->change(now[x],now[y],z);
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i]);
	for(int i=1;i<n;i++)
	{
		int x,y;
		scanf("%d %d",&x,&y);
		buildroad(x,y);
		buildroad(y,x);
	}
	dfs1(1);
	dfs2(1,1);
	root=new node(1,n);
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		int id,x,y,z;
		scanf("%d",&id);
		switch(id)
		{
			case 1:
				scanf("%d %d",&x,&y);
				ask(x,y);
				break;
			case 2:
				scanf("%d %d %d",&x,&y,&z);
				change(x,y,z);
				break;
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值