bzoj3924幻想乡战略游戏 动态点分治+暴力贪心

3924: [Zjoi2015]幻想乡战略游戏

Time Limit: 100 Sec   Memory Limit: 256 MB
Submit: 630   Solved: 291
[ Submit][ Status][ Discuss]

Description

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

Input

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

Output

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

Sample Input

10 5
1 2 1
2 3 1
2 4 1
1 5 1
2 61
2 7 1
5 8 1
7 91
1 10 1
3 1
2 1
8 1
3 1
4 1

Sample Output

0
1
4
5
6

HINT

对于所有数据,1<=c<=1000, 0<=|e|<=1000, n<=105, Q<=105

Source


题目大意:动态求带权树的重心
第二道点分治的题 TT 表示自己好蒻,看不懂nlogn的解,所以研究了一下nlognd的解
其实没啥好说的,就是分治树,然后暴力。先选根节点,暴力出解,然后和其在分治树上的子节点比较即可。
首先介绍暴力的方法,维护d[],val[],fval[]分别表示子树权值,所有子树军队移动到子树根距离,以及所有军队移动到子树根父亲的距离(便于减去来自某一子树方向的解),暴力自己yy有益身心健康
然后其次还要注意的一点是,从根往子树下递归的时候,虽然是递归子树重心,但是比较的节点是和根节点在原树上在这个子树中的根的儿子,所以构树的时候记得要记录
嗯,虽然说和暴力没什么关系,但是学到了一个东东:如果存在一个点y,y是x的一个子节点,使得d[y]*2>树种所有点的权值和(显然反证易得这样的y只有一个),那么y一定比x更优;反之x比y更优。挪动树的重心的办法。。
哎,程序常数仍然大,别人跑30s,我偏偏要跑到40s,郁闷中。。。。(然而卡常数这种东西在100s的题里面是不存在的)

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<cmath>
#define maxn 110000 
#define maxm 220000
#define inf 0x3f3f3f3f
using namespace std;
int read()
{
    char ch = getchar(); int x = 0, f = 1;
    while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
    return x * f;
}

int pre[maxn][2], top;
struct edge {
    int to, next, w;
    void add(int u, int v, int ww, bool p)
    {
        to = v; next = pre[u][p];
        pre[u][p] = top; w = ww;
    }
}e[maxm], e2[maxm];
void adds(int w, int v, int u) {
	e[++top].add(u, v, w, 0);
	e[++top].add(v, u, w, 0);
}

int bin[20], LOG[maxm];
int n, m, G, tot, root, sums, pos[maxn], prt[maxn], son[maxn], f[maxn];
long long mn[maxm][18], val[maxn], d[maxn], dis[maxn], fval[maxn];
bool vis[maxn];

void dfs(int u, int fa) {
	mn[++tot][0] = dis[u]; pos[u] = tot;
	for(int i = pre[u][0]; i; i = e[i].next) 
	if(e[i].to != fa) {
		dis[e[i].to] = dis[u] + e[i].w;
		dfs(e[i].to, u);
		mn[++tot][0] = dis[u];
	}
}

void RMQ_pre() {
	for(int j = 1;j <= LOG[tot]; ++j) 
		for(int i = 1;i + bin[j] - 1 <= tot; ++i)
			mn[i][j] = min(mn[i][j - 1], mn[i + bin[j - 1]][j - 1]);
}
long long rmq(int u, int v) {
	u = pos[u]; v = pos[v];
	if(u > v) swap(u, v);
	int t = LOG[v - u + 1];
	return min(mn[u][t], mn[v - bin[t] + 1][t]);
}
long long dist(int u, int v) {return dis[u] + dis[v] - (rmq(u, v) << 1);}

void G_get(int u, int fa) {
	son[u] = 1; f[u] = 0;
	for(int i = pre[u][0]; i; i = e[i].next)
	if(e[i].to != fa && !vis[e[i].to]) {
		G_get(e[i].to, u);
		son[u] += son[e[i].to];
		f[u] = max(f[u], son[e[i].to]);
	}
	f[u] = max(f[u], sums - son[u]);
	if(f[G] > f[u]) G = u;
}

void Div(int u, int fa) {
	prt[u] = fa; vis[u] = 1; int pre_sum = sums;
	for(int i = pre[u][0]; i; i = e[i].next) 
	if(!vis[e[i].to]) {
		if(son[e[i].to] > son[u]) sums = pre_sum - son[u];
		else sums = son[e[i].to];
		G = 0; G_get(e[i].to, 0);
		e2[++top].add(u, G, e[i].to, 1);
		Div(G, u);
	}
}

void change(int u, int v, int add) {
	val[u] += dist(u, v) * add; 
	if(prt[u]) fval[u] += dist(prt[u], v) * add;
	d[u] += add;
	if(!prt[u]) return;
	change(prt[u], v, add);
}

long long calc(int u) {
	int st = u;
	long long ret = val[u];
	while(prt[u]) {
		long long w = dist(prt[u], st);
		ret += val[prt[u]] - fval[u];
		ret += (d[prt[u]] - d[u]) * w;
		u = prt[u];
	}
	return ret;
}

long long query(int u) {
	long long ans = calc(u);
	for(int i = pre[u][1]; i; i = e2[i].next) 
		if(calc(e2[i].w) < ans) 
			return query(e2[i].to);
	return ans;
}

void init() {
	bin[0] = 1; for(int i = 1;i <= 20; ++i) bin[i] = bin[i - 1] << 1;
	LOG[0] = -1; for(int i = 1;i <= 200000; ++i) LOG[i] = LOG[i >> 1] + 1;
	n = read(); m = read(); 
	for(int i = 1;i < n; ++ i) adds(read(), read(), read());
	top = 0; dfs(1, 0); RMQ_pre();
	G = 0; f[0] = inf; sums = n;
	G_get(1, 0); root = G; Div(root, 0);
}

void solve() {
	while(m--) {
		int u = read(), v = read();
		change(u, u, v);
		printf("%lld\n", query(root));
	}
}

int main()
{
	init();
	solve(); 
	return 0;
}
/*
10 10
1 2 3
2 3 8
3 4 3
4 5 1
5 6 4
2 7 5
2 8 3
1 9 2
1 10 6
4 4
8 6
3 6
1 3
3 3
1 5
1 5
2 6
6 10
9 6



 
*/


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值