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


    (color{#0066ff}{ 题目描述 })

    傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打仗了。

    在打仗之前,幽香现在面临一个非常基本的管理问题需要解决。 整个地图是一个树结构,一共有n块空地,这些空地被n-1条带权边连接起来,使得每两个点之间有一条唯一的路径将它们连接起来。

    在游戏中,幽香可能在空地上增加或者减少一些军队。同时,幽香可以在一个空地上放置一个补给站。 如果补给站在点u上,并且空地v上有dv个单位的军队,那么幽香每天就要花费dv*dist(u,v)的金钱来补给这些军队。

    由于幽香需要补给所有的军队,因此幽香总共就要花费为Sigma(Dv*dist(u,v),其中1<=V<=N)的代价。其中dist(u,v)表示u个v在树上的距离(唯一路径的权和)。

    因为游戏的规定,幽香只能选择一个空地作为补给站。在游戏的过程中,幽香可能会在某些空地上制造一些军队,也可能会减少某些空地上的军队,进行了这样的操作以后,出于经济上的考虑,幽香往往可以移动他的补给站从而省一些钱。

    但是由于这个游戏的地图是在太大了,幽香无法轻易的进行最优的安排,你能帮帮她吗? 你可以假定一开始所有空地上都没有军队。

    (color{#0066ff}{输入格式})

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

    (color{#0066ff}{输出格式})

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

    (color{#0066ff}{输入样例})

    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
    

    (color{#0066ff}{输出样例})

    0
    1
    4
    5
    6
    

    (color{#0066ff}{数据范围与提示})

    对于所有数据,1<=c<=1000, 0<=|e|<=1000, n<=10^5, Q<=10^5 非常神奇的是,对于所有数据,这棵树上的点的度数都不超过20,且N,Q>=1

    (color{#0066ff}{题解})

    这题既然给出了大方的6s,一定是道巨暴力题

    动态点分治题

    建立点分树,每个点维护三个值

    gather[i]代表点分树中以i为根子树的所有点的军队数量 * 该点到i的dis(注意是原树上的dis)

    tofa[i]表示点分树中以i为根的子树对它在点分树上的父亲的gather的贡献

    tot[i]代表点分树中以i为根的子树中的军队数量

    考虑当前补给站在a, 考虑移动到a的儿子b

    那么a除了b的所有子树的军队都会加上一个(dis_{ab})

    b的子树内所有的军队都会减去一个(dis_{ab})

    可以发现,如果某个点更优,当且仅当外面的军队少,里面的军队多

    所以一旦发现b更优,那么最后的补给站一定在b子树内

    所以对于询问,我从点分树的根开始找,一旦发现孩子更优,就在孩子的子树中递归

    现在考虑怎么计算一个点的答案(就是补给站在那个点的答案)

    首先,初始答案是gather[i],这是子树内的贡献

    现在考虑怎么统计子树外的点的贡献

    设dis为当前点到补给站的距离(原树距离)

    用tot[fa[i]] - tot[i]就是父亲子树中出去当前子树的部分的军队数量

    用这个乘上一个dis,就是父亲与当前点的路径的贡献

    这是父亲到当前点的,还有一段是军队到父亲的(军队到父亲+父亲到当前点=军队到当前点)

    这部分为gather[fa[i]] - tofa[i]父亲的总贡献- 当前子树贡献就是其它点到父亲的贡献

    一直统计就行了

    对于修改,在点分树上向上跳(log)然后直接维护三个值就行了

    (O(nlog^2n))

    这是写欧拉序RMQ查询距离的复杂度

    如果写倍增,那复杂度就是(O(nlog^3n)),会TLE

    #include<bits/stdc++.h>
    #define LL long long
    LL in() {
    	char ch; int x = 0, f = 1;
    	while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
    	for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
    	return x * f;
    }
    
    struct node {
    	int to;
    	LL dis;
    	node *nxt;
    	node(int to = 0, LL dis = 0, node *nxt = NULL): to(to), dis(dis), nxt(nxt) {}
    	void *operator new (size_t) {
    		static node *S = NULL, *T = NULL;
    		return (S == T) && (T = (S = new node[1024]) + 1024), S++;
    	}
    };
    const int maxn = 2e5 + 10;
    int siz[maxn], f[maxn], root, sum, n, q, rt, fa[maxn], lg[maxn];
    LL gather[maxn], tofa[maxn],tot[maxn];
    LL st[maxn][26], dfn[maxn], euler, Dis[maxn];
    bool vis[maxn];
    node *h[maxn], *head[maxn];
    void add(int from, int to, int dis, node **hh) {
    	hh[from] = new node(to, dis, hh[from]);
    }
    void dfs(int x, int F, LL dis) {
    	Dis[x] = dis;
    	st[++euler][0] = dis, dfn[x] = euler;
    	for(node *i = h[x]; i; i = i->nxt) {
    		if(i->to == F) continue;
    		dfs(i->to, x, dis + i->dis);
    		st[++euler][0] = dis;
    	}
    }
    void ST() {
    	dfs(1, 0, 0);
    	lg[0] = -1;
    	for(int i = 1; i < maxn; i++) lg[i] = lg[i >> 1] + 1;
    	for(int j = 1; (1 << j) <= euler; j++)
    		for(int i = 1; i + (1 << j) - 1 <= euler; i++)
    			st[i][j] = std::min(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
    }
    LL getdis(int x, int y) {
    	if(dfn[x] > dfn[y]) std::swap(x, y);
    	int len = lg[dfn[y] - dfn[x] + 1];
    	return Dis[x] + Dis[y] - (std::min(st[dfn[x]][len], st[dfn[y] - (1 << len) + 1][len]) << 1);
    }
    
    void getroot(int x, int F) {
    	siz[x] = 1;
    	f[x] = 0;
    	for(node *i = h[x]; i; i = i->nxt) {
    		if(i->to == F || vis[i->to]) continue;
    		getroot(i->to, x);
    		siz[x] += siz[i->to];
    		f[x] = std::max(f[x], siz[i->to]);
    	}
    	f[x] = std::max(f[x], sum - siz[x]);
    	if(f[x] < f[root]) root = x;
    }
    
    void build(int x) {
    	vis[x] = true;
    	for(node *i = h[x]; i; i = i->nxt) {
    		if(vis[i->to]) continue;
    		sum = siz[i->to], root = 0;
    		getroot(i->to, x);
    		add(x, root, i->to, head);
    		fa[root] = x;
    		build(root);
    	}
    }
    void add(int pos, LL val) {
    	tot[pos] += val;
    	for(int i = pos; fa[i]; i = fa[i]) {
    		LL D = getdis(pos, fa[i]);
    		tofa[i] += D * val;
    		tot[fa[i]] += val;
    		gather[fa[i]] += D * val;
    	}
    }
    LL calc(int x) {
    	LL res = gather[x];
    	for(int i = x; fa[i]; i = fa[i]) {
    		LL D = getdis(fa[i], x);
    		res += (tot[fa[i]] - tot[i]) * D;
    		res += gather[fa[i]] - tofa[i];
    	}
    	return res;
    }
    LL query(int x) {
    	LL res = calc(x);
    	for(node *i = head[x]; i; i = i->nxt) 
    		if(calc(i->dis) < res) return query(i->to);
    	return res;
    }
    		
    signed main() {
    	n = in(), q = in();
    	int x, y, z;
    	for(int i = 1; i < n; i++) {
    		x = in(), y = in(), z = in();
    		add(x, y, z, h), add(y, x, z, h);
    	}
    	f[0] = sum = n;
    	getroot(1, 0);
    	int rt = root;
    	build(root);
    	ST();
    	while(q --> 0) {
    		x = in(), y = in();
    		add(x, y);
    		printf("%lld
    ", query(rt));
    	 }
    	return 0;
    }
    
  • 相关阅读:
    explicit
    boolalpha 和 noboolalpha
    C++ 头文件一览
    C++ I/O库总结
    Error:collect2:ld returned 1 exit status (总结)
    常用目录的作用
    硬盘分区与硬软链接
    POJ3694 Network(Tarjan双联通分图 LCA 桥)
    2016"百度之星"
    2016"百度之星"
  • 原文地址:https://www.cnblogs.com/olinr/p/10223262.html
Copyright © 2020-2023  润新知