LG P3345 [ZJOI2015]幻想乡战略游戏(树的带权重心 + 树链剖分+ 动态点分治)

该博客详细解析了ZJOI2015年的一道竞赛题,探讨如何在动态点分治中处理最小值问题。通过转化,发现最小值可以在树的带权重心取得,这里的带权重心是指子树权重的平方大于等于总权重的点中深度最大的那个。博客进一步解释了如何动态修改子树权重,并在树链上使用二分查找,同时处理可能存在的二分过程中取到链外点的问题,利用线段树二分确保单调性。最终,通过边分治解决了度数不超过20的问题,实现了AC的解决方案。
摘要由CSDN通过智能技术生成

题目
首先这个题看起来这个最小值不好在动态点分治时维护。
(居然)可以转化。
最小值于树的带权重心处取到。
带权带的是点权。
树的带权重心是 子 树 权 ∗ 2 > = 总 树 权 子树权*2>=总树权 2>=的点中最深的。
不可能会有多个因为 子 树 权 ∗ 2 > = 总 树 权 子树权*2>=总树权 2>=的点一定形成一条由根出发的链。
那么就可以动态修改子树权,在树链上二分了。
等等,我们都不知道是哪条树链,怎么二分?
因为树链上的点的dfs序递增。
可是万一二分的时候取到了链外的点呢,单调性何在?
求个前缀最大值就有单调性了。。。。。。
可是前缀最大值维护还需要一个 log ⁡ \log log你慢死了
线段树二分啊。。。。。。
最后知道最值点,直接用动态点分治求答案就行了。
因为点的度数<=20,直接用边分治了。
注意边分树深度可能大于 log ⁡ n \log n logn

AC Code:

#include<bits/stdc++.h>
#define maxn 100005
#define lim 30
#define lc now << 1
#define rc now << 1 | 1
#define LL long long
using namespace std;

int n,q;
LL cst[maxn<<1];
int info[maxn],Prev[maxn<<1],to[maxn<<1],cnt_e=1;
void Node(int u,int v,int c){Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,cst[cnt_e]=c;}

bool vis[maxn<<1];
int siz[maxn],id[maxn],pos[maxn],son[maxn],tp[maxn],fa[maxn],tot;

int rt,Min;
void dfs(int now,int ff,int tsz){
	siz[now] = 1;
	for(int i=info[now];i;i=Prev[i])
		if(to[i]!=ff && !vis[i]){
			dfs(to[i],now,tsz);
			siz[now] += siz[to[i]];
			if(Min > max(siz[to[i]],tsz-siz[to[i]]))
				Min = max(siz[to[i]],tsz-siz[to[i]]),
				rt = i;
		}
}

int findrt(int now,int tsz){
	Min = 0x3f3f3f3f , rt = -1;
	dfs(now,0,tsz);
	return rt;
}

int sid[maxn][lim]  , f[maxn][lim] , Dep[maxn];
LL s1[maxn<<1][2],s2[maxn<<1][2], fdis[maxn][lim];

void ser(int now,int ff,int dist,int dep,int pfa,int side){
	f[now][dep] = pfa , fdis[now][dep] = dist , sid[now][dep] = side;
	siz[now] = 1;
	for(int i=info[now];i;i=Prev[i])
		if(to[i]!=ff && !vis[i]){
			ser(to[i],now,dist+cst[i],dep,pfa,side);
			siz[now] += siz[to[i]];
		}
}

void Solve(int now,int dep){
	vis[now] = vis[now^1] = 1;
	ser(to[now],0,0,dep,now,0);
	ser(to[now^1],0,0,dep,now,1);
	
	int rt1 = findrt(to[now],siz[to[now]]);
	if(rt1 == -1) Dep[to[now]] = dep;
	else Solve(rt1,dep+1);
		
	rt1 = findrt(to[now^1],siz[to[now^1]]);
	if(rt1 == -1) Dep[to[now^1]] = dep;
	else Solve(rt1,dep+1);
}

void modifytree(int now,int v){
	for(int i=Dep[now];i>=0;i--)
		s1[f[now][i]][sid[now][i]]+=v , s2[f[now][i]][sid[now][i]]+=v*fdis[now][i];
}

LL query(int now){
	LL ret = 0;
	for(int i=Dep[now];i>=0;i--)
		ret += s1[f[now][i]][sid[now][i]^1] * (fdis[now][i] + cst[f[now][i]])
		+ s2[f[now][i]][sid[now][i]^1];
	return ret;
}

LL add[maxn<<3],Max[maxn<<3];

void dt(int now){
	if(add[now]){
		Max[now] = Max[now] + add[now];
		add[lc] += add[now] , add[rc] += add[now];
		add[now] = 0;
	}
}

void upd(int now){
	Max[now] = max(Max[lc] , Max[rc]);
}

void Insert(int now,int l,int r,int ql,int qr,int v){
	dt(now);
	if(l>qr || ql>r) return;
	if(ql<=l && r<=qr){ add[now]+=v,dt(now);return;}
	int mid = (l+r) >> 1;
	Insert(lc,l,mid,ql,qr,v),Insert(rc,mid+1,r,ql,qr,v);
	upd(now);
}

void modifypath(int now,int v){
	for(;now;now=fa[tp[now]])
		Insert(1,1,n,id[tp[now]],id[now],v);
}

void dfs1(int now,int ff){
	siz[now] = 1 , son[now] = -1;
	fa[now] = ff;
	for(int i=info[now];i;i=Prev[i])
		if(to[i]!=ff){
			dfs1(to[i],now);
			siz[now] += siz[to[i]];
			if(son[now] == -1 || siz[to[i]] > siz[son[now]]) 
				son[now] = to[i];
		}
}

void dfs2(int now,int ff){
	pos[id[now] = ++tot] = now;
	if(son[now]!=-1) tp[son[now]]=tp[now],dfs2(son[now],now);
	for(int i=info[now];i;i=Prev[i])
		if(to[i]!=ff && to[i]!=son[now])
			tp[to[i]] = to[i] , dfs2(to[i],now);
}

int getrt(){
	int now = 1,l=1,r=n,mid;
	for(;l<r;){
		mid = (l+r) >> 1;
		dt(now),dt(lc),dt(rc);
		if(Max[rc]*2 >= Max[1]) now = rc , l = mid + 1;
		else now = lc , r = mid;
	}
	return pos[l];
}

int main(){
	scanf("%d%d",&n,&q);
	for(int i=1;i<n;i++){
		int u,v,c;
		scanf("%d%d%d",&u,&v,&c);
		Node(u,v,c),Node(v,u,c);
	}
	dfs1(1,0),tp[1]=1,dfs2(1,0);
	Solve(findrt(1,n),0);
	for(int u,e;q--;){
		scanf("%d%d",&u,&e);
		modifytree(u,e);
		modifypath(u,e);
		printf("%lld\n",query(getrt()));
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值