Loading

ZJOI 2015 幻想乡战略游戏(动态点分治)

题意

https://loj.ac/problem/2135

思路

首先要明确一点,答案分布是有单调性的。什么意思呢?假设我们的答案在 \(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])\) 。那么就能 \(O(\log n)\) 的询问取某个点的答案是多少了。

修改仍为暴力修改,没什么好说的。

关键在于回答询问,我们从整张图的重心 \(u​\) 开始跳,这个重心 \(u​\) 将树劈成了若干个连通块(题目有条件,最多 \(20​\) 个),对于其中一个连通块,我们设 \(v​\) 在这个连通块内且与 \(u​\) 有边。我们查询选 \(v​\) 的得到的答案,如果小于等于 \(u​\) 的答案,那么就要跳了。妙的是这里跳的是这个连通块的重心 \(w​\),这样就可以保证复杂度了。所以,在点分的时候就要顺便把每个层重心 \(u​\) 的下层重心 \(w​\) ,以及对应的 \(v​\) 存下来。复杂度 \(O(20 \cdot n\log ^2n)​\) ,还是挺飘的。

代码

#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;
}
posted @ 2019-01-10 13:47  Paulliant  阅读(188)  评论(0编辑  收藏  举报