描述
給你一個圖,一共有 N 個點,2*N-2 條有向邊。
邊目錄按兩部分給出
1、 開始的 n-1 條邊描述了一顆以 1 號點爲根的生成樹,即每個點都可以由 1 號點到達。
2、 接下來的 N-1 條邊,一定是從 i 到 1(2<=i<=N)的有向邊,保證每個點都能到達
有 q 次詢問:
1 x w :表示將第 x 條邊的邊權修改爲 w
2 u v :詢問 u 到 v 的最短距離
【輸入格式】
第一行是 2 個整數 N,Q,表示一共 N 個點 Q 次詢問
接下來是 N-1 行,每行 3 個整數 U,V,W,表示了前 N-1 條邊,u 到 v 的有向邊
接下來 N-1 行,每行 3 個整數 U,V,W,描述了每個點到 1 號點的邊,V==1
接下來是 Q 行,表示 Q 次修改與詢問
【輸出格式】
若干行,每行回答一次詢問
【輸入樣例】
5 9
1 3 1
3 2 2
1 4 3
3 5 4
5 1 5
3 1 6
2 1 7
4 1 8
2 1 1
2 1 3
2 3 5
2 5 2
1 1 100
2 1 3
1 8 30
2 4 2
2 2 4
【輸出樣例】
0
1
4
8
100
132
10
第一反應,樹鏈剖分+線段樹,這麼裸,欺負我昨天晚上纔看的樹剖板
誒等等?怎麼會有邊權?
然後這個只會敲板子的蒟蒻就懵逼掉了
然後今天蒟蒻的玄學貪心1分兒也沒騙到
於是本蒟蒻成功爆0
關於正解
令 ai表示從 i 到根的邊的長度。對於每個點維護 disti表示從根到 i 的路徑, mindisti表示 i 的子樹中 distj+aj的最小值。
然後對於詢問 (u,v)分類:
如果 u 是 v的祖先,由於權值都是正整數,答案爲distv−distu。
否則答案爲mindistu−distu+distv。
dist和 mindist用 dfs序+線段樹維護就可以了。
具體維護
維護子樹 1->i->1 的值 dist,每個點記錄第一次的 dfs 序 st[i],子樹結束的 ed[i]
當邊 u->v 修改爲 w,則 st[v]…ed[v] 增加 w- w’,w’表示 u->v 原來的值。同時,當邊 u->1 修改爲 w:則 st[u]..st[u]增加 w-w’
好了,那麼搬上代碼
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 200010
using namespace std;
struct Edge{
int u,v,w,nxt;
}e[N<<1];
long long sum[N<<2],p[N<<2],dis[N],add[N<<2];
int dep[N],top[N],son[N],l[N],r[N],s[N],u[N],w[N],x1,y1,z1;
int h[N],tot;
int k,cnt=0,n,Q,a[N],L;
void addd(int x,int y,int z){
e[++cnt].u=x;e[cnt].v=y,e[cnt].w=z;e[cnt].nxt=h[x],h[x]=cnt,u[y]=x;
}
//以下dfs序與lca部分
void dfs1(int x,int y){
dep[x]=++y;l[x]=++tot;s[x]=1;w[tot]=x;
for(int i=h[x];i;i=e[i].nxt){
dis[e[i].v]=dis[x]+e[i].w;
dfs1(e[i].v,y);
s[x]+=s[e[i].v];
if(s[e[i].v]>s[son[x]])son[x]=e[i].v;
}
r[x]=tot;
}
void dfs2(int x,int y){
top[x]=y;
if(son[x])dfs2(son[x],y);
for(int i=h[x];i;i=e[i].nxt)
if(e[i].v!=son[x])dfs2(e[i].v,e[i].v);
}
int Lca(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]>dep[top[y]])
x=u[top[x]];
else y=u[top[y]];
}
return dep[x]>dep[y]?y:x;
}
//以下線段樹部分(謝天謝地我終於搞出了沒有結構體的線段樹)
void Up(int x){
sum[x]=min(sum[x<<1],sum[x<<1|1]);
}
void Build(int x,int l,int r){
if(l==r){
sum[x]=dis[w[l]]+a[w[l]];
add[x]=dis[w[l]];
return;
}
int Mid=l+r>>1;
Build(x<<1,l,Mid);
Build(x<<1|1,Mid+1,r);
Up(x);
}
inline void Down(int x){
if(p[x]){
sum[x<<1]+=p[x];add[x<<1]+=p[x];p[x<<1]+=p[x];
sum[x<<1|1]+=p[x];add[x<<1|1]+=p[x];p[x<<1|1]+=p[x];
p[x]=0;
}
}
inline void Update1(int x,int l,int r,int y,int z){
if(l==r){
sum[x]+=z;
return;
}
Down(x);
int Mid=l+r>>1;
if(y<=Mid)Update1(x<<1,l,Mid,y,z);else Update1(x<<1|1,Mid+1,r,y,z);
Up(x);
}
inline void Update2(int x,int l,int r,int L,int R,int y){
if(l>R||r<L)return;
if(l>=L&&r<=R){
sum[x]+=y;add[x]+=y;p[x]+=y;
return;
}
Down(x);
int Mid=l+r>>1;
Update2(x<<1,l,Mid,L,R,y);Update2(x<<1|1,Mid+1,r,L,R,y);
Up(x);
}
long long Query(int x,int l,int r,int L,int R){
if(l>R||r<L)return 1e18;
if(l>=L&&r<=R)return sum[x];
Down(x);
int Mid=l+r>>1;
return min(Query(x<<1,l,Mid,L,R),Query(x<<1|1,Mid+1,r,L,R));
}
long long Query2(int x,int l,int r,int y){
if(l==r)return add[x];
Down(x);
int Mid=l+r>>1;
if(y<=Mid)return Query2(x<<1,l,Mid,y);
return Query2(x<<1|1,Mid+1,r,y);
}
int main(){
scanf("%d%d",&n,&Q);
for(int i=1;i<n;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
addd(x,y,z);
}
for(int i=1;i<n;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
e[++cnt].u=x;e[cnt].v=y;a[x]=z;
}
tot=0;
dfs1(1,0);
dfs2(1,1);
Build(1,1,n);
while(Q--){
scanf("%d%d%d",&k,&x1,&y1);
if(k==1){
if(x1>=n)Update1(1,1,n,l[e[x1].u],y1-a[e[x1].u]),a[e[x1].u]=y1;
else Update2(1,1,n,l[e[x1].v],r[e[x1].v],y1-e[x1].w),e[x1].w=y1;
}else{
L=Lca(x1,y1);
if(L==x1)printf("%lld\n",Query2(1,1,n,l[y1])-Query2(1,1,n,l[x1]));
else printf("%lld\n",Query(1,1,n,l[x1],r[x1])-Query2(1,1,n,l[x1])+Query2(1,1,n,l[y1]));
}
}
return 0;
}
我想我的代碼比起某標程已經相當好看了,只要是知道線段樹與樹剖的結合題解都應該看得懂,除了個別變量名在改動時被懶惰的本蒟蒻張冠李戴之外沒有什麼難以理解的地方了。
ps(據說有隔壁巨佬3h就調好了還嫌慢我這個6h的並不敢發言)