CODE[VS]3306 水果姐逛水果街3 树链剖分+线段树

题目链接
题目描述 Description
连续两天都没有被cgh难倒,水果姐心情很不错,第三天又来逛水果街。
今天cgh早早就来到了水果街等水果姐,因为他带来了帮手cys。cgh的魔法是把水果街变成树结构,而cys的魔法是瞬间改变某家店的水果价格。一边问一边改,绝不给水果姐思考的时间!
同样还是n家水果店,编号为1~n,每家店能买水果也能卖水果,并且同一家店卖与买的价格一样。
cgh和cys一共有m个操作。
操作的格式为
0 x y 获 1 x y
如果操作类型是0,表示cys把第x家店的价格改为y
如果操作类型是1,则表示要求水果姐从第x家店出发到第y家店,途中只能选一家店买一个水果,然后选一家店(可以是同一家店,但不能往回走)卖出去。并且输出最多可以赚多少钱。
这题是给天牛做的。
输入描述 Input Description
第一行n,表示有n家店
下来n个正整数,表示每家店一个苹果的价格。
下来n-1行,每行两个整数x,y,表示第x家店和第y家店有一条边。
下来一个整数m,表示下来有m个操作。
下来有m行,每行三个整数,表示一次操作。
输出描述 Output Description
每行对应一个1类型的操作,输出一个整数,表示面对cgh的每次询问,水果姐最多可以赚到多少钱。
水题分析 Waterproblem Analysis
本来想写一篇关于树链剖分模板题的博客,发现意义不大因为要写这一篇博客,相较于模板有一些提升但基本操作都包括了用来练手很不错。为什么要引入树链剖分这一个算法,相较于水果姐逛水果街2,这道题目只多了一个单点修改的操作。该系列的第二道题目我是用倍增+LCA做的,他不支持单点修改的操作,或者说处理起来非常麻烦,时间复杂度很高与我们省时间的做法不相吻合。支持单点修改比较快的数据结构,以我的知识范围应该就是线段树了,但是线段树处理的是一个序列,而题目中给出的是一颗树,这样我们就需要要树链剖分的算法,将树转化成一条一条的链方便我们进行操作。这样在运用线段树的操作方法就可以AC了。
介绍一下树链剖分的一些基本知识。
size[u]是以u为根的子树的节点数。fa[u]是u节点的父亲。son[u]是u节点的重儿子。dep[u]是u节点的深度。树链剖分是将树上的路径划分成重链与轻链,重链是由重儿子组成的链,重儿子是父亲节点的儿子中size最大的节点。这些都可以由第一次dfs求出。

void dfs1(int u,int f,int depth)
{
	fa[u]=f;
	size[u]=1;
	son[u]=0;
	dep[u]=depth;
	for(int pp=h[u];pp;pp=next[pp])
	{
		int v=vis[pp];
		if(v==fa[u])continue;
		dfs1(v,u,depth+1);
		size[u]+=size[v];
		if(size[son[u]]<size[v])son[u]=v;
	}
}

top[u]是u所在节点的链端,pos[u]是u节点在线段树中的位置。val[u]是u节点的权值,这些可以由第二次dfs求出。

void dfs2(int u,int tp)
{
	num++;
	top[u]=tp;
	pos[u]=num;val[num]=a[u];
	if(son[u])dfs2(son[u],tp);
	for(int pp=h[u];pp;pp=next[pp])
	{
		int v=vis[pp];
		if(v==fa[u]||v==son[u])continue;
		dfs2(v,v);
	}
}

接下来的操作如果是在同一条链上那么操作就与线段树的操作一样了直接区间操作或者单点操作即可。那么如果两个端点不在同一条链上,那么我们就要将他们跳到同一条链上,在跳的过程中,对经过的链再进行操作即可。因为一个节点的重儿子的size一定大于它size的一半所以我们每次操作最多操作节点size的一半,而进行下一次操作又是另一个节点size的一半,那么对于整个区间我们的复杂度也可以做到logN级别了。

void change_list(ll u,ll v,ll w)
{
    while(top[u]!=top[v])
    {
        if(dep[top[u]]<dep[top[v]])swap(u,v);
        change_tree(1,pos[top[u]],pos[u],w);
        u=fa[top[u]];
    }
    if(dep[u]<dep[v])swap(u,v);
    change_tree(1,pos[v],pos[u],w);
}

树链剖分的内容介绍到这里也就差不多了。接下来介绍一下这个题的思路。对于单点修改这个操作过于简单就不在赘述。我们来考虑从x->y这个路径上的答案买要早于卖。我们在寻找x->y的路径时,总是要把他们跳到一条链上去,如果单纯的记录区间最大,最小,收益并不能满足题目中先买后卖的条件,所以我们要记录两个收益,一个是up_profit,down_profit,分别表示向上走的收益与向下走的收益。记录起点x,终点y。这样如果我们操作y让它向上跳时,我们就要从down_profit中取得答案,操作x让它向上跳时,就要从up_profit中取得答案。那么这两个数
怎么取得呢?其实很简单,因为我们在dfs的过程中,浅的点一定在左儿子,深的点一定在右儿子,那么很容易就可以求出来。

void pushup(int d)
{
	tree[d].max=max(tree[d<<1].max,tree[d<<1|1].max);
	tree[d].min=min(tree[d<<1].min,tree[d<<1|1].min);
	tree[d].upprofit=max(tree[d<<1].max-tree[d<<1|1].min,
	max(tree[d<<1].upprofit,tree[d<<1|1].upprofit));
	tree[d].downprofit=max(tree[d<<1|1].max-tree[d<<1].min,
	max(tree[d<<1].downprofit,tree[d<<1|1].downprofit));
}

这样在操作的过程中如果我们操作y时记录一个maxx,操作x时记录一个minn,这样每次都再更新一个ans这样基本上就可以AC了。我因为在最后一条链上忘记更新ans值得了60分,引以为戒。
附上代码:

#include<iostream>//丑丑丑
#include<cstdio> 
#define MAXN 1000010
#define ll long long
using namespace std;
ll n,m,edgen=0,num=0;
ll a[MAXN],next[MAXN],h[MAXN],vis[MAXN],fa[MAXN],dep[MAXN],size[MAXN],son[MAXN],top[MAXN],pos[MAXN],val[MAXN];
struct re
{
	ll l,r,max,min,upprofit,downprofit;
}tree[MAXN];
inline ll get_num()
{
	ll num=0;
	char c;
	bool flag=1;
	while((c=getchar())==' '||c=='\r'||c=='\n');
	if(c=='-')flag=0;
	else num=c-'0';
	while(isdigit(c=getchar()))
	num=num*10+c-'0';
	return num*(flag?1:-1);
} 
void add(int u,int v)
{
	edgen++;
	next[edgen]=h[u];
	h[u]=edgen;
	vis[edgen]=v;
} 
void dfs1(int u,int f,int depth)
{
	fa[u]=f;
	size[u]=1;
	son[u]=0;
	dep[u]=depth;
	for(int pp=h[u];pp;pp=next[pp])
	{
		int v=vis[pp];
		if(v==fa[u])continue;
		dfs1(v,u,depth+1);
		size[u]+=size[v];
		if(size[son[u]]<size[v])son[u]=v;
	}
}
void dfs2(int u,int tp)
{
	num++;
	top[u]=tp;
	pos[u]=num;val[num]=a[u];
	if(son[u])dfs2(son[u],tp);
	for(int pp=h[u];pp;pp=next[pp])
	{
		int v=vis[pp];
		if(v==fa[u]||v==son[u])continue;
		dfs2(v,v);
	}
}
void pushup(int d)
{
	tree[d].max=max(tree[d<<1].max,tree[d<<1|1].max);
	tree[d].min=min(tree[d<<1].min,tree[d<<1|1].min);
	tree[d].upprofit=max(tree[d<<1].max-tree[d<<1|1].min,max(tree[d<<1].upprofit,tree[d<<1|1].upprofit));
	tree[d].downprofit=max(tree[d<<1|1].max-tree[d<<1].min,max(tree[d<<1].downprofit,tree[d<<1|1].downprofit));	
}
void build(int d,int l,int r)
{
	tree[d].l=l;
	tree[d].r=r;
	if(tree[d].l==tree[d].r)
	{
		tree[d].max=val[l];
		tree[d].min=val[l];
		tree[d].upprofit=0;
		tree[d].downprofit=0;
		return;
	}
	int mid=(l+r)>>1;
	build(d<<1,l,mid);
	build(d<<1|1,mid+1,r);
	pushup(d);
}
void change(int d,int l,int w)
{
	if(tree[d].l==l&&tree[d].r==l)
	{
		tree[d].max=w;
		tree[d].min=w;
		return;
	}
	int mid=(tree[d].l+tree[d].r)>>1;
	if(l<=mid) change(d<<1,l,w);
	else change(d<<1|1,l,w);
	pushup(d);
}
ll find_max(ll d,ll l,ll r)
{
	if(tree[d].l==l&&tree[d].r==r)
	{
		return tree[d].max;
	}
	ll mid=(tree[d].l+tree[d].r)>>1;
	if(r<=mid)
	{
		return find_max(d<<1,l,r);
	}
	else
	{
		if(l>mid)
		{
			return find_max(d<<1|1,l,r);
		} 
		else
		{
			return max(find_max(d<<1,l,mid),find_max(d<<1|1,mid+1,r));
		}
	}
}
ll find_min(ll d,ll l,ll r)
{
	if(tree[d].l==l&&tree[d].r==r)
	{
		return tree[d].min;
	}
	ll mid=(tree[d].l+tree[d].r)>>1;
	if(r<=mid)
	{
		return find_min(d<<1,l,r);
	}
	else
	{
		if(l>mid)
		{
			return find_min(d<<1|1,l,r); 
		}
		else
		{
			return min(find_min(d<<1,l,mid),find_min(d<<1|1,mid+1,r));
		}
	}
}
ll find_tree(ll d,ll l,ll r,ll s)
{

	if(tree[d].l==l&&tree[d].r==r)
	{
		if(s)
		{
		return tree[d].upprofit;			
		}
		else
		{
		return tree[d].downprofit;			
		}
	}
	ll mid=(tree[d].l+tree[d].r)>>1;
	if(r<=mid)
	{
		return find_tree(d<<1,l,r,s);
	}
	else
	{
		if(l>mid)
		{
			return find_tree(d<<1|1,l,r,s);
		}
		else
		{
			ll maxx,minn;
			if(s)
			{
			    maxx=find_max(1,l,mid);
			    minn=find_min(1,mid+1,r); 				
			}
            else
            {
            	maxx=find_max(1,mid+1,r);
            	minn=find_min(1,l,mid);
            }
			return max(maxx-minn,max(find_tree(1,l,mid,s),find_tree(1,mid+1,r,s)));
		} 
	}
}
ll find_list(ll u,ll v)
{
	ll ans=-132819023192,up=1,down=0,maxx=-2134392847234,minn=2147483647000;
	while(top[u]!=top[v])
	{
		if(dep[top[u]]>dep[top[v]])
		{
			ans=max(find_tree(1,pos[top[u]],pos[u],up),ans);
			minn=min(minn,find_min(1,pos[top[u]],pos[u]));
			ans=max(ans,maxx-minn);
			u=fa[top[u]];
		}
		else
		{
			ans=max(find_tree(1,pos[top[v]],pos[v],down),ans);
			maxx=max(maxx,find_max(1,pos[top[v]],pos[v]));
			ans=max(ans,maxx-minn);
			v=fa[top[v]]; 
		}
	}
	if(dep[u]>dep[v])
	{
	ans=max(find_tree(1,pos[v],pos[u],up),ans);	
	ans=max(find_max(1,pos[v],pos[u])-minn,ans);
	minn=min(minn,find_min(1,pos[v],pos[u])); 
	ans=max(ans,maxx-find_min(1,pos[v],pos[u]));
	}
	else
	{
	ans=max(find_tree(1,pos[u],pos[v],down),ans);
	ans=max(maxx-find_min(1,pos[u],pos[v]),ans);
	maxx=max(maxx,find_max(1,pos[u],pos[v]));
	ans=max(find_max(1,pos[u],pos[v])-minn,ans);				
	} 
	ans=max(maxx-minn,ans);	
	return ans;
}
int main()
{
	n=get_num();
	for(int i=1;i<=n;i++)a[i]=get_num();
	for(int i=1;i<n;i++)
	{
		ll u,v;
		u=get_num();
		v=get_num();
		add(u,v);
		add(v,u); 
	} 
	dfs1(1,0,1);
	dfs2(1,1);
	build(1,1,n);
//	for(int i=1;i<=10;i++)cout<<tree[i].l<<" "<<tree[i].r<<'\n';
	m=get_num();
	for(int i=1;i<=m;i++)
	{
		ll q=get_num();
		if(q==0)
		{
			ll u,w;
			u=get_num();
			w=get_num();
			change(1,pos[u],w);
		}
		if(q==1)
		{
			ll u,v;
			u=get_num();
			v=get_num();
			if(u==v)cout<<0<<'\n';
			else
			cout<<find_list(u,v)<<'\n';
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值