CF1192B/CEOI2019 動態直徑 Dynamic Diameter【尤拉序-線段樹】
題目解析
唔,首先有一個比較顯然的大概是\(nq\)級別複雜度的做法,就是暴力修改,然後\(dfs\)兩次算出直徑。
(不過這沒有什麼用就是了
因為有修改,我們嘗試把樹下下來,放到序列上,用線段樹維護。
樹鏈剖分?
不,太麻煩喏,我們知道尤拉序這個東西是可以辦到的,並且我們之前就用它的性質求過\(lca\):兩個點對應的尤拉序區間中,深度最小的點就是這兩個點的\(lca\)。
具體而言,設\(u\)在尤拉序中第一次出現的位置為\(l\),\(v\)在尤拉序中第一次出現的位置為\(r\),尤拉序陣列為\(E[]\),那麼\(lca=E[x],l<=x<=r,dep[E[x]]<=dep[E[l...r]]\)
如果不知道這個結論可以看看 這個
那麼\(dist(u,v)=dis[u]+dis[v]-2\times dis[lca]\),\(dis[i]\)指從\(1\)到\(i\)的距離。
根據定義,樹的直徑是所有點對\(dist\)的最大值
如果用線段樹維護(相當於是線段樹維護與尤拉序陣列對應的點的\(dis\),長度為\(2n-1\),完成把樹變成序列的任務),那對於區間\([l,r]\)的答案長這個樣子:
\(max\{dis[i]+dis[j]-2\times dis[lca(i,j)]\},l<=i<=j<=r\)
我們注意到\(lca\)對於\(i,j\)來說是一個\(rmq\)
沒有必要。
具體到這道題而言,由於邊權都是正數,所以\(lca\)也是\(dis\)最小的那個點,所以可以把答案化為這個樣子:
\(max\{dis[i]+dis[j]-2\times dis[k] \},l<=i<=k<=j<=r\)
因為\(dis\)前面是負號,所以讓這個值取\(max\)的那個位置一定是\(dis[k]\)最小的那個點,所以可以不用再維護\([i,j]\)之間的最小值,而是在維護上述答案的時候自然就處理了(我感覺這裡網上很多題解都沒有說清楚,我看了好久)
接下來考慮怎麼維護上述答案,主要是要便於區間合併。
我們可以把答案看成三部分:\(dis[i],dis[j],-2\times dis[k]\)
對於每個區間,維護:
\(mx=max(dis[i]),l<=i<=r\)
\(mn=min(dis[i]),l<=i<=r\),(主要是為了\(-2\times dis[k]\),維護\(max(-dis[i])\)也可
\(res=max\{dis[i]+dis[j]-2\times dis[k] \},l<=i<=k<=j<=r\)
為了方便左右兒子合併,也就是\(i,j\)可以來源於左右不同兒子,合在一起是一個更有的答案,我們還需要維護以下資訊:
\(rmx=max(dis[i]-2\times dis[j]),i<=j\),這個可以看成我們還需要在右邊找一個右端點來組成答案。
同理,\(lmx=max(dis[i]-2\times dis[j]),i<=j\)
然後維護線段樹:
\(t.mx=max(lc.mx,rc.mx)\)
\(t.mn=min(lc.mn,rc.mn)\)
\(t.res=max(max(lc.res,rc.res),max(lc.rmx+rc.mx,rc.lmx+lc.mx))\)
\(t.rmx=max(max(lc.rmx,rc.rmx),lc.mx-2rc.mn)\)
\(t.lmx=max(max(lc.lmx,rc.lmx),rc.mx-2lc.mn)\)
最後查詢的時候是查詢整棵樹的直徑,所以不用寫\(query\)
修改的時候,答案只會對這條邊深度較大的點的子樹的答案產生影響,所以對子樹進行更新即可(子樹在尤拉序中是連續一段區間
►Code View
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
#define N 200005
#define M 200005
#define MOD 998244353
#define INF 0x3f3f3f3f
#define LL long long
LL rd()
{
LL x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-') f=-1; c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
return f*x;
}
struct edge{
int nxt,v;LL w;
}e[M<<1];
int hd[N],cnt=1;
void add(int u,int v,LL w)
{
e[++cnt].nxt=hd[u];
e[cnt].v=v;
e[cnt].w=w;
hd[u]=cnt;
}
struct node{
LL res/*直徑*/,mx/*dis最大*/,mn/*dis最小*/;
LL rmx/*dis_i-2dis_j(i<=j)的最大值 差一個右端點*/,lmx/*同理 dis_i-2dis_j(i>=j)的最大值*/;
}tree[N<<2];
LL tag[N<<2];
int n,Q;
LL W;
LL dis[N];
int E[N<<1],tim;//尤拉序
int d1[N],d2[N];//出去進來的時間 即點i在尤拉序中的最左/最右位置
void dfs(int u,int fa)
{
E[++tim]=u;
d1[u]=tim;
for(int i=hd[u];i;i=e[i].nxt)
{
int v=e[i].v;LL w=e[i].w;
if(v==fa) continue;
dis[v]=dis[u]+w;
dfs(v,u);
E[++tim]=u;
}
d2[u]=tim;
}
node Merge(node x,node y)
{
node t;
t.mx=max(x.mx,y.mx);
t.mn=min(x.mn,y.mn);
t.rmx=max(max(x.rmx,y.rmx),x.mx-2*y.mn);
t.lmx=max(max(x.lmx,y.lmx),y.mx-2*x.mn);
t.res=max(max(x.res,y.res),max(x.mx+y.lmx,y.mx+x.rmx));
return t;
}
void PushUp(int i)
{
tree[i]=Merge(tree[i<<1],tree[i<<1|1]);
}
void Build(int i,int l,int r)
{
if(l==r)
{
int u=E[l];
tree[i].mx=tree[i].mn=dis[u];
tree[i].rmx=tree[i].lmx=-dis[u];//dis_u-2*dis_u=-dis_u
tree[i].res=0;//一個點不能構成直徑
tag[i]=0;
return ;
}
int mid=(l+r)>>1;
Build(i<<1,l,mid);
Build(i<<1|1,mid+1,r);
PushUp(i);
return ;
}
void Modify(int i,LL val)
{
tree[i].mx+=val,tree[i].mn+=val;
tree[i].lmx-=val,tree[i].rmx-=val;//delta=(dis_i-val)-2*(dis_j-val)-(dis_i-2*dis_j)
tag[i]+=val;
}
void PushDown(int i)
{
if(tag[i])
{
Modify(i<<1,tag[i]);
Modify(i<<1|1,tag[i]);
tag[i]=0;
}
}
void Update(int i,int l,int r,int ql,int qr,LL val)
{
if(ql<=l&&r<=qr)
{
Modify(i,val);
return ;
}
PushDown(i);
int mid=(l+r)>>1;
if(ql<=mid) Update(i<<1,l,mid,ql,qr,val);
if(qr>mid) Update(i<<1|1,mid+1,r,ql,qr,val);
PushUp(i);
}
int main()
{
n=rd(),Q=rd(),W=rd();
for(int i=1;i<=n-1;i++)
{
int u=rd(),v=rd();LL w=rd();
add(u,v,w);
add(v,u,w);
}
dfs(1,0);
Build(1,1,tim);
LL ans=0;
while(Q--)
{
int id=rd(),u;LL d=rd();
id=(id+ans)%(n-1)+1;
d=(d+ans)%W;
if(dis[e[id<<1].v]<dis[e[id<<1|1].v]) id=(id<<1|1),u=e[id].v;
else id=(id<<1),u=e[id].v;
Update(1,1,tim,d1[u],d2[u],d-e[id].w);
e[id].w=e[id^1].w=d;
ans=tree[1].res;
printf("%lld\n",ans);
}
return 0;
}
/*
修改是給的邊的編號 這個時候還是寫前向星好一點吧
(怎麼感覺vector一無是處了qwq
*/