ZJOI 2015 幻想鄉戰略遊戲(動態點分治)
題意
思路
首先要明確一點,答案分佈是有單調性的。什麼意思呢?假設我們的答案在 \(u\) 節點,\((u,v)\) 之間有一條邊且 \(u\) 離答案所在的點更近,那麼 \(u\) 節點作為答案一定不比在 \(v\) 節點作答案劣。從鏈的角度分析在拓展到樹上會比較好理解這個性質。
那麼如果樹是一棵完全二叉樹,就可以 \(\log n\) 的回答了。從根節點向下跳,每次如果找到一個 \(\displaystyle2*sum[u]\geq tot\) (\(sum[u]\) 為 \(u\) 子樹點權之和, \(tot\) 為整張圖點權之和)的節點,就說明存在一個更優或不劣的答案,就往這個方向跳,直到不能跳為止。更新時直接暴力修改父親,沒什麼可說的。
雖然這棵樹不一定是完全二叉樹,但是這棵樹的“分治樹”高度一定是不超過 \(\log n\) 的(我個人不習慣使用“分治樹”的概念,而將點分治理解為重心管轄區域這樣的概念)。那麼就對這棵樹進行點分,存下每個數對應的每一層重心對應容器的標號,到它的距離,以及是容是斥。此題的每層重心的容器就是兩個變數 \(Sum,sum\) ,其中 \(Sum\) 表示此重心管轄區域權值乘距離總和,\(sum\) 表示權值總和。那麼對於 \(u\) 一層重心對應容器編號為 \(id\) ,距離 \(dis\) ,容斥係數為 \(s\) ,產生貢獻即為 \(s(Sum[id]+dis\cdot sum[id])\)
修改仍為暴力修改,沒什麼好說的。
關鍵在於回答詢問,我們從整張圖的重心 \(u\) 開始跳,這個重心 \(u\) 將樹劈成了若干個連通塊(題目有條件,最多 \(20\) 個),對於其中一個連通塊,我們設 \(v\) 在這個連通塊內且與 \(u\) 有邊。我們查詢選 \(v\) 的得到的答案,如果小於等於 \(u\) 的答案,那麼就要跳了。妙的是這裡跳的是這個連通塊的重心 \(w\),這樣就可以保證複雜度了。所以,在點分的時候就要順便把每個層重心 \(u\) 的下層重心 \(w\) ,以及對應的 \(v\)
程式碼
#include<bits/stdc++.h>
#define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
#define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
using namespace std;
template<typename T,typename _T>inline bool chk_min(T &x,const _T y){return y<x?x=y,1:0;}
template<typename T,typename _T>inline bool chk_max(T &x,const _T y){return x<y?x=y,1:0;}
typedef long long ll;
const int N=1e5+5;
template<const int maxn,const int maxm>struct Linked_list
{
int head[maxn],to[maxm],nxt[maxm],cost[maxm],tot;
Linked_list(){clear();}
void clear(){memset(head,-1,sizeof(head));tot=0;}
void add(int u,int v,int w){to[++tot]=v,cost[tot]=w,nxt[tot]=head[u],head[u]=tot;}
#define EOR(i,G,u) for(int i=G.head[u];~i;i=G.nxt[i])
};
Linked_list<N,N<<1>G,H;
ll sum[N*2],Sum[N*2];int Sc;
int lv[N],Sid[N][40];ll dis[N][40];bool sgn[N][40];
int sz[N];bool mark[N];
int n,q;
void CFS(int u,int f,int tot,int &C,int &Mi)
{
sz[u]=1;int res=0;
EOR(i,G,u)
{
int v=G.to[i];
if(v==f||mark[v])continue;
CFS(v,u,tot,C,Mi);
sz[u]+=sz[v];
res=max(res,sz[v]);
}
res=max(res,tot-sz[u]);
if(chk_min(Mi,res))C=u;
}
void dfs_init(int u,int f,ll D,bool s)
{
Sid[u][++lv[u]]=Sc,dis[u][lv[u]]=D,sgn[u][lv[u]]=s;
EOR(i,G,u)
{
int v=G.to[i],w=G.cost[i];
if(v==f||mark[v])continue;
dfs_init(v,u,D+w,s);
}
}
void dac(int u,int f,int tot)
{
int rt=u,Mi=1e9;
CFS(u,0,tot,u,Mi);
if(f)H.add(f,u,rt);
mark[u]=1;
Sc++,dfs_init(u,0,0,1);
EOR(i,G,u)
{
int v=G.to[i],w=G.cost[i];
if(mark[v])continue;
Sc++,dfs_init(v,u,w,0);
dac(v,u,sz[u]>sz[v]?sz[v]:tot-sz[u]);
}
}
void update(int u,int val)
{
FOR(i,1,lv[u])
{
int id=Sid[u][i];ll d=dis[u][i];
sum[id]+=val;
Sum[id]+=(ll)val*d;
}
}
ll query(int u)
{
ll res=0;
FOR(i,1,lv[u])
{
int id=Sid[u][i];ll d=dis[u][i];bool s=sgn[u][i];
if(s)res+=Sum[id]+d*sum[id];
else res-=Sum[id]+d*sum[id];
}
return res;
}
ll Query(int u)
{
ll res=query(u);
EOR(i,H,u)
{
int v=H.to[i],w=H.cost[i];
if(query(w)<=res)return Query(v);
}
return res;
}
int main()
{
scanf("%d%d",&n,&q);
FOR(i,1,n-1)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
G.add(u,v,w),G.add(v,u,w);
}
int C,Mi=1e9;
CFS(1,0,n,C,Mi);
memset(mark,0,sizeof(mark));
memset(lv,0,sizeof(lv));
Sc=0;
dac(1,0,n);
while(q--)
{
int u,val;
scanf("%d%d",&u,&val);
update(u,val);
printf("%lld\n",Query(C));
}
return 0;
}