BZOJ 3924 Zjoi2015 幻想乡战略游戏 动态树分治

题目大意:给定一棵树,每个点有一个点权,多次改变某个点的点权,多次查询带权重心到所有点的带权距离之和

此生无悔入东方,来世愿生幻想乡

首先我们考虑如何计算一个点到所有点的带权距离之和且支持修改

用动态树分治就好了嘛。。。

每个点记录子树中带权距离之和,以及权值之和,再在每个子树中记录一个需要减掉的版本

然后一直向上扫到根就能统计了

↑这段话面对会写动态树分治的人,不会的先去切捉迷藏吧

然后就好搞了。。。

对于分治结构的每一个点,我们枚举它的出边

如果某条出边连向的点的距离之和小于当前点,那么答案一定在那条出边指向的子树中,分治做下去就行了

如果不存在小于当前点的出边,那么当前点就是重心

注意区分【出边指向的点】和【分治子节点】的区别= =

时间复杂度O(nlog^2n*20)

求两点间距离要用RMQLCA否则复杂度多个log BZ上过不去

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define M 100100
using namespace std;
int n,m;
namespace Tree{
	struct abcd{
		int to,f,next;
		bool ban;
	}table[M<<1];
	int head[M],tot=1;
	int fa[M],dpt[M],dis[M];
	int log_2[M<<1],pos[M],a[M<<1][20];
	void Add(int x,int y,int z)
	{
		table[++tot].to=y;
		table[tot].f=z;
		table[tot].next=head[x];
		head[x]=tot;
	}
	void DFS(int x)
	{
		static int T=0;
		int i;
		dpt[x]=dpt[fa[x]]+1;
		a[pos[x]=++T][0]=dis[x];
		for(i=head[x];i;i=table[i].next)
			if(table[i].to!=fa[x])
			{
				fa[table[i].to]=x;
				dis[table[i].to]=dis[x]+table[i].f;
				DFS(table[i].to);
				a[++T][0]=dis[x];
			}
	}
	void Build_LCA()
	{
		int i,j;
		for(i=2;i<=n-1<<1;i++)
			log_2[i]=log_2[i>>1]+1;
		for(j=1;j<=log_2[n-1<<1];j++)
			for(i=1;i+(1<<j)-1<=n-1<<1;i++)
				a[i][j]=min(a[i][j-1],a[i+(1<<j-1)][j-1]);
	}
	int LCA_Distance(int x,int y)
	{
		x=pos[x];y=pos[y];
		if(x>y) swap(x,y);
		int l=log_2[y-x+1];
		return min(a[x][l],a[y-(1<<l)+1][l]);
	}
	int Distance(int x,int y)
	{
		return dis[x]+dis[y]-2*LCA_Distance(x,y);
	}
}
namespace Dynamic_TDC{
	struct abcd{
		int to,first,next;
	}_table[M];
	int _head[M],_tot;
	int root,fa[M];
	long long dis_sum1[M],dis_sum2[M],sum1[M],sum2[M];
	void Add(int x,int y,int z)
	{
		_table[++_tot].to=y;
		_table[_tot].first=z;
		_table[_tot].next=_head[x];
		_head[x]=_tot;
	}
	int Get_Size(int x,int from)
	{
		int i,size=1;
		for(i=Tree::head[x];i;i=Tree::table[i].next)
		{
			if(Tree::table[i].ban)
				continue;
			if(Tree::table[i].to==from)
				continue;
			size+=Get_Size(Tree::table[i].to,x);
		}
		return size;
	}
	int Get_Centre_Of_Gravity(int x,int from,int size,int &cg)
	{
		int i,re=1,flag=1;
		for(i=Tree::head[x];i;i=Tree::table[i].next)
		{
			if(Tree::table[i].ban)
				continue;
			if(Tree::table[i].to==from)
				continue;
			int temp=Get_Centre_Of_Gravity(Tree::table[i].to,x,size,cg);
			if(temp<<1>size)
				flag=0;
			re+=temp;
		}
		if(size-re<<1>size)
			flag=0;
		if(flag)
			cg=x;
		return re;
	}
	int Tree_Divide_And_Conquer(int x)
	{
		int i,size=Get_Size(x,0);
		Get_Centre_Of_Gravity(x,0,size,x);
		for(i=Tree::head[x];i;i=Tree::table[i].next)
		{
			if(Tree::table[i].ban)
				continue;
			Tree::table[i].ban=true;
			Tree::table[i^1].ban=true;
			int temp=Tree_Divide_And_Conquer(Tree::table[i].to);
			fa[temp]=x;
			Add(x,temp,Tree::table[i].to);
		}
		return x;
	}
	void Modify(int x,int delta)
	{
		int i;
		sum1[x]+=delta;
		for(i=x;fa[i];i=fa[i])
		{
			int dis=Tree::Distance(x,fa[i]);
			dis_sum1[fa[i]]+=(long long)dis*delta;
			dis_sum2[i]+=(long long)dis*delta;
			sum1[fa[i]]+=delta;
			sum2[i]+=delta;
		}
	}
	long long Calculate(int x)
	{
		int i;
		long long re=dis_sum1[x];
		for(i=x;fa[i];i=fa[i])
		{
			int dis=Tree::Distance(x,fa[i]);
			re+=dis_sum1[fa[i]]-dis_sum2[i];
			re+=(sum1[fa[i]]-sum2[i])*dis;
		}
		return re;
	}
	long long Query(int x)
	{
		int i;
		long long cost=Calculate(x);
		for(i=_head[x];i;i=_table[i].next)
		{
			long long temp=Calculate(_table[i].first);
			if(temp<cost)
				return Query(_table[i].to);
		}
		return cost;
	}
}
namespace IStream{
	#define L (1<<16)
	char Get_Char()
	{
		static char buffer[L],*S,*T;
		if(S==T)
		{
			T=(S=buffer)+fread(buffer,1,L,stdin);
			if(S==T) return EOF;
		}
		return *S++;
	}
	int Get_Int()
	{
		bool flag=false;
		char c;
		int re=0;
		do c=Get_Char(); while((c<'0'||c>'9')&&c!='-');
		if(c=='-')
			flag=true,c=Get_Char();
		while(c>='0'&&c<='9')
			re=(re<<1)+(re<<3)+(c-'0'),c=Get_Char();
		return flag?-re:re;
	}
}
struct OStream{
	char buffer[L],*S;
	OStream()
	{
		S=buffer;
	}
	void Put_Char(char c)
	{
		*S++=c;
		if(S==buffer+L)
			fwrite(buffer,1,L,stdout),S=buffer;
	}
	void Put_Long_Long(long long x)
	{
		static int stack[20],top;
		if(!x) stack[++top]='0';
		while(x)
			stack[++top]=x%10+'0',x/=10;
		while(top)
			Put_Char(stack[top--]);
		Put_Char('\n');
	}
	~OStream()
	{
		fwrite(buffer,1,S-buffer,stdout);
	}
}os;
int main()
{
	using namespace IStream;
	int i,x,y,z;
	cin>>n>>m;
	for(i=1;i<n;i++)
	{
		x=Get_Int();
		y=Get_Int();
		z=Get_Int();
		Tree::Add(x,y,z);
		Tree::Add(y,x,z);
	}
	Tree::DFS(1);
	Tree::Build_LCA();
	Dynamic_TDC::root=Dynamic_TDC::Tree_Divide_And_Conquer(1);
	for(i=1;i<=m;i++)
	{
		x=Get_Int();y=Get_Int();
		Dynamic_TDC::Modify(x,y);
		long long temp=Dynamic_TDC::Query(Dynamic_TDC::root);
		os.Put_Long_Long(temp);
	}
	return 0;
}
时间: 2024-10-01 07:34:19

BZOJ 3924 Zjoi2015 幻想乡战略游戏 动态树分治的相关文章

BZOJ 3924: [Zjoi2015]幻想乡战略游戏(动态点分治)

这种动态点分治嘛,GDKOI时听打到了,也有同学讲到了,所以印象比较深刻也就想出来了,然后就在实现方面卡了好久= = 不得不说CLJ说得真的太简单了,实现方面根本没提. 首先我们可以先用树分治构建出这棵树的分治树,也就是把这棵树的重心作为根节点然后子树为他的子树的重心这样递归下去,然后每个节点存的是其子树的信息. 对于每个节点我们保存这个子树的dv的总和已经把该节点作为点的答案值 这样对于修改能在log n的时间内解决 寻找答案的时候,我们可以发现,如果现在节点的子树dv和*2大于总节点,那么向

【bzoj3924】[Zjoi2015]幻想乡战略游戏 动态树分治

题目描述 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打仗了. 在打仗之前,幽香现在面临一个非常基本的管理问题需要解决. 整个地图是一个树结构,一共有n块空地,这些空地被n-1条带权边连接起来,使得每两个点之间有一条唯一的路径将它们连接起来.在游戏中,幽香可能在空地上增加或者减少一些军队.同时,幽香可以在一个空地上放置一个补给站. 如果补给站在点u上,并

[ZJOI2015]幻想乡战略游戏 - 动态点分治

先考虑无修要怎么操作. 发现在无修的情况下,我们可以用一个换根\(dp\)解决. 那么带修改的情况要怎么办呢? 每次修改重新\(dp\)一遍不就行了(雾. 好的,让我们先来敲一个\(O(N^2)\)的\(dp\). #include <bits/stdc++.h> using namespace std; typedef long long ll; inline ll ty() { char ch = getchar(); ll x = 0, f = 1; while (ch < '0'

luogu P3345 [ZJOI2015]幻想乡战略游戏 |动态点分治

题目描述 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打仗了. 在打仗之前,幽香现在面临一个非常基本的管理问题需要解决. 整个地图是一个树结构,一共有n块空地,这些空地被n-1条带权边连接起来,使得每两个点之间有一条唯一的路径将它们连接起来. 在游戏中,幽香可能在空地上增加或者减少一些军队.同时,幽香可以在一个空地上放置一个补给站. 如果补给站在点u上,

loj 2135 「ZJOI2015」幻想乡战略游戏 - 动态点分治

题目传送门 传送门 题目大意 给定一棵树,初始点权都为0,要求支持: 修改点权 询问带权重心 询问带权重心就在点分树上跑一下就行了.(枚举跳哪个子树更优) 剩下都是基础点分治. 学了一下11-dimensional的2.2k动态点分治,然后写抄出来只有1.9k??? Code /** * loj * Problem#2135 * Accepted * Time: 4492ms * Memory: 28404k */ #include <bits/stdc++.h> using namespac

bzoj3924 [Zjoi2015]幻想乡战略游戏 点分树,动态点分

[BZOJ3924][Zjoi2015]幻想乡战略游戏 Description 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打仗了. 在打仗之前,幽香现在面临一个非常基本的管理问题需要解决. 整个地图是一个树结构,一共有n块空地,这些空地被n-1条带权边连接起来,使得每两个点之间有一条唯一的路径将它们连接起来.在游戏中,幽香可能在空地上增加或者减少一些军

[ZJOI2015]幻想乡战略游戏 解题报告 (动态点分治)

[ZJOI2015]幻想乡战略游戏 题意 有一棵大小为 \(n\) 的带权树, 每个点有一个权值, 权值可以修改 \(q\) 次, 找出一个补给点 \(x\) , 使得 \(\sum_{u \in V} val[u] \times dis(x,u)\) 最小, 并求出这个最小值. 一句话 : 求带权重心 (zsy说的) 附加条件 : 树中所有点的度数不超过 \(20\). 思路 一道你以为复杂度过不了, 但其实是过得了的题. 首先, 我们假定先选了一个点 \(u\) 作为补给点, 我们可以算出它

[Luogu3345][ZJOI2015]幻想乡战略游戏

Luogu 题意: 动态维护带权重心. sol 这是一道写起来很舒服的动态点分治.(不像某些毒瘤题) 我们考虑,如果你选择的补给点不是当前的带权重心,那么带权重心就在补给点的一个子树中(你把补给点当做根的话).那么,你把补给点向带权重心所在的子树中移动的时候,答案一定会减小.换言之,如果补给点无论向哪个方向移动答案都不会减小,那么这个点就是带权重心. 所以我们每次考虑移动补给点然后计算即可. 但是怎么移动呢? 最优复杂度的移动策略是:先假设点分树的根就是补给,然后你一次检查与它在原树中相连的所有

luogu P3345 [ZJOI2015]幻想乡战略游戏(点分树)

题意自己看... 思路 没想到今(昨)天刷着刷着点分治的水题,就刷出来了一个点分树... 然后就疯狂地找题解,代码,最后终于把它给弄懂了. 点分树--动态点分治,对于此题来说,我们发现设u为当前的补给站位置,v是它的一个儿子.同时设dis(i,j)为树上i点到j点的距离.sumi为以i为跟的子树中d(也就是军队数)的总量 我们把补给站从u转移到v,答案的变化为dis(u,v)*(sumu-sumv)-dis(u,v)*sumv=dis(u,v)*(sumu-2*sumv) 所以当2*sumv>s