P3345 [ZJOI2015]幻想乡战略游戏
考虑先随便找一个点作为根,然后再慢慢移动根,这样一步步走到最优的点
设 $sum[x]$ 表示节点 $x$ 的子树的军队数,$len(x,y)$ 表示 $x,y$ 之间边的长度
那么对于根节点 $x$ 的一个儿子 $v$,考虑把儿子搞为根时,代价的改变量
$v$ 的子树内的军队消耗减少,共减少了 $sum[v]\cdot len(x,v)$
$v$ 的子树外的军队消耗增加,即根节点 $x$ 的子树内除了 $v$ 子树的军队消耗增加
代价增加了 $(sum[x]-sum[v])\cdot len(x,y)$
如果儿子比父亲优,那么整理可得 $2sum[v]>sum[x]$,显然满足条件的 $v$ 只有一个
此时如果没有满足的 $v$ ,那么 $x$ 就是最优点,否则最优点在 $x$ 的子树内
如果每次都一个一个儿子跳下去,显然会GG
但是因为最优点在子树内所以可以考虑在点分树上跳
我们需要维护两个东西 : $S[x],Sf[x]$,分别表示节点 $x$的点分树子树到 $x$ 的总代价,节点 $x$ 的点分树子树到 $x$ 在点分树父亲 $Fa[x]$ 的总代价
那么计算一个节点 $x$ 的总消耗就考虑一直往 $Fa$ 跳,每次跳完就考虑这一段产生的代价
设当前跳到了节点 $now$
那么十分显然 $Fa[now]$ 的点分树子树 不包括 $now$ 的点分树子树 的部分新产生的代价为 $S[Fa[now]]-Sf[now]+(sum[Fa[now]]-sum[now])\cdot dis(Fa[now],x)$
($dis(x,y)$表示节点 $x,y$ 在原树上的距离,注意此时 $sum[x]$ 表示节点 $x$ 的点分树子树军队总数)
我们可以用 RMQ 求 LCA 来 $O(1)$ 求出两点间的距离
至于修改操作也在点分树上直接维护就好了
注意$long long$,代码有注释
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<vector> using namespace std; typedef long long ll; inline int read() { int x=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if(ch=='-') f=-1; ch=getchar(); } while(ch>='0'&&ch<='9') { x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); } return x*f; } const int N=2e5+7,INF=1e9+7; int fir[N],from[N],to[N],val[N],cntt; inline void add(int &a,int &b,int &c) { from[++cntt]=fir[a]; fir[a]=cntt; to[cntt]=b; val[cntt]=c; } int n,m,tot,rt; int sz[N],mx[N],Fa[N]; vector <int> V[N],G[N];//存点分树 bool vis[N]; void find_rt(int x,int fa) { sz[x]=1; mx[x]=0; for(int i=fir[x];i;i=from[i]) { int &v=to[i]; if(v==fa||vis[v]) continue; find_rt(v,x); sz[x]+=sz[v]; mx[x]=max(mx[x],sz[v]); } mx[x]=max(mx[x],tot-sz[x]); if(mx[x]<mx[rt]) rt=x; } void build(int x)//建点分树 { vis[x]=1; for(int i=fir[x];i;i=from[i]) { int &v=to[i]; if(vis[v]) continue; tot=sz[v]; rt=0; find_rt(v,0); V[x].push_back(rt); G[x].push_back(v); Fa[rt]=x; build(rt); } } int st[N],dfn[N],pos[N],dis[N],Top,dfs_clock,f[N][21],Log[N];//维护RMQ求LCA维护dis void dfs(int x,int fa) { dfn[x]=++dfs_clock; st[++Top]=x; pos[x]=Top; for(int i=fir[x];i;i=from[i]) { int &v=to[i]; if(v==fa) continue; dis[v]=dis[x]+val[i]; dfs(v,x); st[++Top]=x; } } void pre() { Log[0]=-1; for(int i=1;i<=Top;i++) Log[i]=Log[i>>1]+1; for(int i=1;i<=Top;i++) f[i][0]=st[i]; for(int i=1;(1<<i)<=Top;i++) for(int j=1;j+(1<<i-1)<=Top;j++) { if(dfn[f[j][i-1]]<dfn[ f[j+(1<<i-1)][i-1] ]) f[j][i]=f[j][i-1]; else f[j][i]=f[j+(1<<i-1)][i-1]; } } inline int LCA(int x,int y) { int l=pos[x],r=pos[y]; if(l>r) swap(l,r); int k=Log[r-l+1]; if(dfn[f[l][k]]<dfn[f[r-(1<<k)+1][k]]) return f[l][k]; return f[r-(1<<k)+1][k]; } inline int Dis(int x,int y) { return dis[x]+dis[y]-2*dis[LCA(x,y)]; } ll sum[N],S[N],Sf[N];//注意long long inline void change(int x,int y)//修改操作 { sum[x]+=y; for(int now=x;Fa[now];now=Fa[now])//在点分树上跳 { int d=Dis(x,Fa[now]); Sf[now]+=1ll*d*y; S[Fa[now]]+=1ll*d*y; sum[Fa[now]]+=y; } } inline ll calc(int x)//计算以x为根的花费 { ll res=S[x]; for(int now=x;Fa[now];now=Fa[now]) { int d=Dis(x,Fa[now]); res+=S[Fa[now]]-Sf[now]+(sum[Fa[now]]-sum[now])*d; } return res; } ll query(int x)//点分树上暴力dfs找最优解 { ll res=calc(x); int len=V[x].size(); for(int i=0;i<len;i++) { ll t=calc(G[x][i]);//注意是算G[x][i] if(t<res) return query(V[x][i]);//注意是往V[x][i]跳 } return res; } int main() { int a,b,c,RT; n=read(); m=read(); for(int i=1;i<n;i++) { a=read(),b=read(),c=read(); add(a,b,c); add(b,a,c); } tot=n; mx[0]=INF; find_rt(1,0); RT=rt; build(rt); dfs(1,0); pre(); while(m--) { a=read(); b=read(); change(a,b); printf("%lld\n",query(RT)); } return 0; }