題目鏈接
題目描述 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';
}
}
}