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


    题目描述

     傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打仗了。 在打仗之前,幽香现在面临一个非常基本的管理问题需要解决。 整个地图是一个树结构,一共有n块空地,这些空地被n-1条带权边连接起来,使得每两个点之间有一条唯一的路径将它们连接起来。在游戏中,幽香可能在空地上增加或者减少一些军队。同时,幽香可以在一个空地上放置一个补给站。 如果补给站在点u上,并且空地v上有dv个单位的军队,那么幽香每天就要花费dv×dist(u,v)的金钱来补给这些军队。由于幽香需要补给所有的军队,因此幽香总共就要花费为Sigma(Dv*dist(u,v),其中1<=V<=N)的代价。其中dist(u,v)表示u个v在树上的距离(唯一路径的权和)。 因为游戏的规定,幽香只能选择一个空地作为补给站。在游戏的过程中,幽香可能会在某些空地上制造一些军队,也可能会减少某些空地上的军队,进行了这样的操作以后,出于经济上的考虑,幽香往往可以移动他的补给站从而省一些钱。但是由于这个游戏的地图是在太大了,幽香无法轻易的进行最优的安排,你能帮帮她吗? 你可以假定一开始所有空地上都没有军队。

    输入

    第一行两个数n和Q分别表示树的点数和幽香操作的个数,其中点从1到n标号。 
    接下来n-1行,每行三个正整数a,b,c,表示a和b之间有一条边权为c的边。 
    接下来Q行,每行两个数u,e,表示幽香在点u上放了e单位个军队
    (如果e<0,就相当于是幽香在u上减少了|e|单位个军队,说白了就是du←du+e)。
    数据保证任何时刻每个点上的军队数量都是非负的。 
    1<=c<=1000, 0<=|e|<=1000, n<=10^5, Q<=10^5
    对于所有数据,这个树上所有点的度数都不超过20
    N,Q>=1

    输出

    对于幽香的每个操作,输出操作完成以后,每天的最小花费,也即如果幽香选择最优的补给点进行补给时的花费。 

    样例输入

    10 5
    1 2 1
    2 3 1
    2 4 1
    1 5 1
    2 6 1
    2 7 1
    5 8 1
    7 9 1
    1 10 1
    3 1
    2 1
    8 1
    3 1
    4 1

    样例输出

    0
    1
    4
    5
    6


    题解

    动态点分治

    考虑到带权重心一定在当前点到距离与权重总和更小的方向上(没有则当前点为重心),并且这个方向是唯一的,因此可以每次修改都这样移动,把重心找出。

    然而直接移动就是直接暴力,需要更优雅的做法。

    考虑在子树中移动,可以使用动态点分治的点分树,这样树高只有$log n$,就可以在每层之间移动重心。

    那么只需要想办法求出所有带权点到某个点的距离,并支持修改即可。

    由于一个子树以外的点到子树中某点距离可以看作先到根,再从根到该点。所以可以考虑容斥的方法,即用子树中所有点到该点的距离&个数减去子节点的子树中所有点到该点的距离&个数。

    然后维护这两个数组即可。由于保证了每个点的度数不超过20,因此每次暴力移动重心,直到所有子节点都比它大为止,此时当前点就是重心。

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

    #include <cstdio>
    #include <algorithm>
    #define N 100010
    using namespace std;
    typedef long long ll;
    int head[N] , to[N << 1] , len[N << 1] , next[N << 1] , cnt , log[N << 1] , pos[N] , tot;
    int si[N] , mx[N] , sum , root , vis[N] , fa[N] , val[N << 1] , rt , num[N];
    ll deep[N] , md[20][N << 1] , va[N] , vb[N];
    void add(int x , int y , int z)
    {
    	to[++cnt] = y , len[cnt] = z , next[cnt] = head[x] , head[x] = cnt;
    }
    void dfs(int x , int fa)
    {
    	int i;
    	pos[x] = ++tot , md[0][tot] = deep[x];
    	for(i = head[x] ; i ; i = next[i])
    		if(to[i] != fa)
    			deep[to[i]] = deep[x] + len[i] , dfs(to[i] , x) , md[0][++tot] = deep[x];
    }
    ll dis(int x , int y)
    {
    	ll t = deep[x] + deep[y];
    	x = pos[x] , y = pos[y];
    	if(x > y) swap(x , y);
    	int k = log[y - x + 1];
    	return t - 2 * min(md[k][x] , md[k][y - (1 << k) + 1]);
    }
    void getroot(int x , int fa)
    {
    	int i;
    	si[x] = 1 , mx[x] = 0;
    	for(i = head[x] ; i ; i = next[i])
    		if(!vis[to[i]] && to[i] != fa)
    			getroot(to[i] , x) , si[x] += si[to[i]] , mx[x] = max(mx[x] , si[to[i]]);
    	mx[x] = max(mx[x] , sum - si[x]);
    	if(mx[x] < mx[root]) root = x;
    }
    void solve(int x)
    {
    	int i;
    	vis[x] = 1;
    	for(i = head[x] ; i ; i = next[i])
    		if(!vis[to[i]])
    			sum = si[to[i]] , root = 0 , getroot(to[i] , 0) , fa[root] = x , val[i] = root , solve(root);
    }
    ll calc(int x)
    {
    	int i;
    	ll ans = 0;
    	for(i = x ; i ; i = fa[i]) ans += va[i] + num[i] * dis(x , i);
    	for(i = x ; fa[i] ; i = fa[i]) ans -= vb[i] + num[i] * dis(x , fa[i]);
    	return ans;
    }
    ll query()
    {
    	int x = rt , i , y;
    	ll mn , t;
    	while(1)
    	{
    		mn = calc(x) , y = x;
    		for(i = head[x] ; i ; i = next[i])
    			if(val[i] && (t = calc(to[i])) < mn)
    				mn = t , y = val[i];
    		if(x == y) break;
    		x = y;
    	}
    	return mn;
    }
    int main()
    {
    	int n , m , i , j , x , y , z;
    	scanf("%d%d" , &n , &m);
    	for(i = 1 ; i < n ; i ++ ) scanf("%d%d%d" , &x , &y , &z) , add(x , y , z) , add(y , x , z);
    	dfs(1 , 0);
    	for(i = 2 ; i <= tot ; i ++ ) log[i] = log[i >> 1] + 1;
    	for(i = 1 ; (1 << i) <= tot ; i ++ )
    		for(j = 1 ; j <= tot - (1 << i) + 1 ; j ++ )
    			md[i][j] = min(md[i - 1][j] , md[i - 1][j + (1 << (i - 1))]);
    	mx[0] = 1 << 30 , sum = n , getroot(1 , 0) , rt = root , solve(root);
    	while(m -- )
    	{
    		scanf("%d%d" , &x , &y);
    		for(i = x ; i ; i = fa[i]) va[i] += y * dis(x , i) , num[i] += y;
    		for(i = x ; fa[i] ; i = fa[i]) vb[i] += y * dis(x , fa[i]);
    		printf("%lld
    " , query());
    	}
    	return 0;
    }
    

     

  • 相关阅读:
    算法
    什么叫「人的格局」?是否有必要培养大的格局或怎么培养?
    套接字
    信号量 P V测试详解
    Emacs快速入门
    Emacs 从入门到精通
    Linux多进程之间的文件锁
    mysql 登录报错:ERROR 1045 (28000)
    类职责协作模型
    .Netcore使用Session
  • 原文地址:https://www.cnblogs.com/GXZlegend/p/7465051.html
Copyright © 2020-2023  润新知