幻想乡战略游戏

题解:幻想乡战略游戏

原题:传送门
给定点的权值,求树上所有点到一点的 路径*权值 之和的最小值。要求在线修改点权,在线回答。
如果是单纯的静态查询,我们可以用点分治来做。但是动态呢?总不可能修改一次做一次点分吧,复杂度会达到 \(O(n^2logn)\) 。那我们就不能用点分治来做了吗?不,我们还有动态点分治!

前置:点分树

整棵树的结构是不变的,被修改的只有点权。那么,我们每一次点分选到的重心也是不变的(动态点分治的前提)。如果我们把点分治每一次选到的中心之间连边,就可以构成一棵高度为 logn 的树。
这棵树,就是我们的点分树(动态点分治)
点分树的构建:对于当前的以 u 为根的树,找到它的重心,删去重心,再对每棵子树递归子过程,并让各子树的重心与 u 的重心连边。只需要在原来的分治板子上加一句 fa[rt]=u;
修改嘛,一般直接在点分树上往上跳就行了。
这里直接套用 lsy 大佬的代码:

inline void work(int u,int father){
  vis[u]=1,fa[u]=father;
  for(int i=head[u];i;i=nex[i]){
    int v=var[i];
    Size=siz[v];rt=0;
    getroot(v,u);
    Addedge(u,rt,v);
    work(rt,u);
  }
}

象征性的Addedge,因为我们可能会需要对点分树中某一个点的子树进行区分,我们就可以通过Addedge时记录的 v 来进行区分。
对于点分树上的每个节点,保存它的所有子树中的一些信息,然后,对于每一次修改,我们就在点分树中从当前节点往上跳,时间复杂度为 logn (毕竟点分树的高不会超过log n)。

那么对于这道题呢?
先画个图帮助理解:
无标题.png

Q1 : 统计答案

我们知道,点分治的统计答案通常是在子树中找到重心再进行容斥,那么动态点分治呢?
仍然类似。
我们假设 v 的子树中的所有节点到 v 的答案已经统计好,现在要求 u 的所有子树中 除了 v 和它的子树 以外的其它的节点到 v 的答案。
对着图比划一下?


sum[u]表示以 u 为根的树中所有节点的点权之和。
求出 u 的答案之后,我们得消除 v 及其子树对于答案的影响,再加上 \((sum[u]-sum[v]) * dist(u,v)\)
不妨再定义两个数组 ans,ansf。
ans[u]表示 u 的所有子树中的节点 到 u 点的 路径 * 点权之和。
ansf[v]表示 v 的所有子树中(当然也包括了v)的点 到v的父节点(也就是u)的路径* 点权之和。
ans[u]-ansf[v]即是 u 的所有子树中 除去v和v的子树以外的 节点到u点的答案。
仍然是容斥。

\[ans[u]-ansf[v]+(sum[u]-sum[v])*dist(u,v) \]

再求 u及其子树节点外 的节点对答案的贡献呢?只需要在点分树上跳到父节点,再重复上述过程即可。

inline ll calc(int x){
  ll ret=ans[x];
  for(int i=x;p[i];i=p[i]){
	ll l=dis(p[i],x);
	ret+=ans[p[i]]-ansf[i];
	ret+=(sum[p[i]]-sum[i])*l;
  }
  return ret;
}

修改点权时,我们也只需要在点分树上跳,维护三个数组即可。

Q2 : 更新最优解

那么,问题解决了吗?没有!经过测试,我们发现,每一次找答案如果从树根开始找的话会 T,会被卡成 \(O(n^2)\) 。怎么办呢?
解决方法是:从上一次的答案点开始找。
还是上面的那个图,我们从 u 转移到 v,那么在 u 的子树内,答案的变化量就为

\[\Delta ans = (sum[u]-sum[v])*dist(u,v) - sum[v]*dist(u,v) \]

也就是\(\Delta ans = (sum[u]-2*sum[v])*dist(u,v)\)
显然,只有当 \(sum[v]>sum[u]/2\) 时,答案才可能会变得更优。而这样的点,最多只有一个。
因此,我们可以直接利用上一次询问的答案来进行答案更新,具体来说,就是依次扫描与它相邻的每一个点,并往答案更优的那个点更新,再重复,直至找到最优解。

inline ll query(int x){
  ll anss=calc(x);
  for(int i=head[x];i;i=nex[i]){
	int v=var[i];
	ll tmp=calc(v);
    if(tmp<anss) return query(v);//更新最优解 
  }
  last=x;return anss;
}

顺带着学了一下树上 RMQ,然而依旧有点迷 。
代码实现借鉴了这位大佬:https://blog.csdn.net/qq_34564984/article/details/53791482

#include<bits/stdc++.h>
#define ll long long
const int N=1e5+1;
using namespace std;
bool vis[N];
int n,Q,rt,cnt,last=1,Size,f[N],p[N],s[N<<1],id[N],ip[N],dep[N<<1],siz[N],st[N<<1][20];
ll ans[N],ansf[N],dist[N<<1],sum[N];
int head[N],nex[N<<1],var[N<<1],edge[N<<1];
template<typename S>inline void in(S &r){//快读 
	r=0;int f=1;char c='a';
	do{c=getchar();if(c=='-') f=-1;}while(!isdigit(c));
	do{r=(r<<3)+(r<<1)+c-'0';c=getchar();}while(isdigit(c));
	r*=f;
}
inline void add(int x,int v,int w){//加边 
	var[++cnt]=v,edge[cnt]=w,nex[cnt]=head[x],head[x]=cnt;
}
inline void gr(int x,int fa){//找重心 
	siz[x]=1,f[x]=0;
	for(int i=head[x];i;i=nex[i]){
		int v=var[i];
		if(vis[v]||v==fa) continue;
		gr(v,x);
		siz[x]+=siz[v],f[x]=max(f[x],siz[v]);
	}
	f[x]=max(f[x],Size-siz[x]);
	if(f[x]<f[rt]) rt=x;
}
inline void build(int x){//建点分树 
	vis[x]=1;
	for(int i=head[x];i;i=nex[i]){
		int v=var[i];
		if(vis[v]) continue;
		Size=siz[v],rt=0;
		gr(v,0);p[rt]=x;
		build(rt);
	}
}
inline void dfs(int x,int fa){
	s[++cnt]=x;
	if(!id[x]) id[x]=cnt;
	dep[cnt]=dep[ip[fa]]+1;ip[x]=cnt;
	for(int i=head[x];i;i=nex[i]){
		int v=var[i];
		if(v==fa) continue;
		dist[v]=dist[x]+edge[i];
		dfs(v,x);s[++cnt]=x,dep[cnt]=dep[ip[fa]+1];
	}
}
inline void make(){
	for(int i=1;i<=cnt;++i) st[i][0]=i;
	for(int j=1;j<=18;++j)
		for(int i=1;i<=cnt;++i) if(i+(1<<j)-1<=cnt){
			int x=st[i][j-1],y=st[i+(1<<j-1)][j-1];
			if(dep[x]<dep[y]) st[i][j]=x;
			else st[i][j]=y;
		}
}
inline int query2(int l,int r){
	int len=r-l+1,i=0;
	while((1<<i+1)<=len) ++i;
	if(dep[st[l][i]]<dep[st[r-(1<<i)+1][i]]) return st[l][i];
	else return st[r-(1<<i)+1][i];
}//O(1)LCA 
inline int lca(int x,int y){
	if(id[x]>id[y]) swap(x,y);
	return s[query2(id[x],id[y])];
}
inline ll dis(int x,int y){
	int c=lca(x,y);
	return dist[x]+dist[y]-2*dist[c];
}//LCA求路径长 
inline void update(int x,int val){
	sum[x]+=val;
	for(int i=x;p[i];i=p[i]){
		ll l=dis(p[i],x);
		sum[p[i]]+=val;//p[i]子树中总的点权 
		ansf[i]+=val*l;//i子树中所有节点到他爹的距离 
		ans[p[i]]+=val*l;//p[i]子树中所有节点到他的距离 
	}
}
inline ll calc(int x){
	ll ret=ans[x];
	for(int i=x;p[i];i=p[i]){
		ll l=dis(p[i],x);
		ret+=ans[p[i]]-ansf[i];//核心 
		ret+=(sum[p[i]]-sum[i])*l;
	}
	return ret;
}
inline ll query(int x){
	ll anss=calc(x);
	for(int i=head[x];i;i=nex[i]){
		int v=var[i];
		ll tmp=calc(v);
		if(tmp<anss) return query(v);//更新最优解 
	}
	last=x;return anss;
}
int main(){
	in(n),in(Q);
	for(int i=1,a,b,c;i<n;++i){
		in(a),in(b),in(c);
		add(a,b,c),add(b,a,c);
	}
	cnt=0;dfs(1,0);make();//LCA套装 
	Size=f[0]=n;
	gr(1,0);build(rt);//建点分树 
	int a,b;
	while(Q--){
		in(a),in(b);
		update(a,b);//更新当前节点值 
		printf("%lld\n",query(last));//从上一次的最优解开始寻找 
	}
	return 0;
}
posted @ 2019-06-02 19:24  JT_kk  阅读(268)  评论(0编辑  收藏  举报