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

17 篇文章 0 订阅
3 篇文章 0 订阅

Description


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

PDF版试题

数据保证任何时刻每个点上的军队数量都是非负的。
1<=c<=1000, 0<=|e|<=1000, n<=10^5, Q<=10^5
对于所有数据,这个树上所有点的度数都不超过20
N,Q>=1

Solution


要求带权重心嘛,我们只需要随便找一个起点然后往权减小的方向走就行了。我们建出点分树在点分树上贪心,由于度数不超过20因此不会卡成O(n)
考虑怎么快速求一个点作为根时的答案。我们每个节点记录s1[x]表示d的和,s2[x]表示x子树内到x的答案,s3[x]表示x子树内到fa[x]的答案,统计的时候在点分树上暴力跳然后容斥就可以了

为了做到两个log我们需要rmq求lca

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <math.h>
#define rep(i,st,ed) for (register int i=st;i<=ed;++i)

typedef long long LL;
const int INF=0x3f3f3f3f;
const int N=200005;

struct Graph {
	struct edge {int y,w,next;} e[N];

	int ls[N],edCnt;

	edge operator [](int x) {
		return e[x];
	}

	void add_edge(int x,int y,int w) {
		e[++edCnt]=(edge) {y,w,ls[x]}; ls[x]=edCnt;
	}
} G,T;

int fa[N],size[N],mxSon[N];
int lg[N],rec,sum,cnt;
int mn[19][N],dis[N],pos[N];

LL s1[N],s2[N],s3[N],ans;

bool del[N];

int read() {
	int x=0,v=1; char ch=getchar();
	for (;ch<'0'||ch>'9';v=(ch=='-')?(-1):(v),ch=getchar());
	for (;ch<='9'&&ch>='0';x=x*10+ch-'0',ch=getchar());
	return x*v;
}

int max(int x,int y) {
	return x>y?x:y;
}

int min(int x,int y) {
	return x<y?x:y;
}

void get_root(int now,int from) {
	size[now]=1; mxSon[now]=0;
	for (int i=G.ls[now];i;i=G[i].next) {
		if (del[G[i].y]||G[i].y==from) continue;
		get_root(G[i].y,now); size[now]+=size[G[i].y];
		mxSon[now]=max(mxSon[now],size[G[i].y]);
	}
	mxSon[now]=max(mxSon[now],sum-size[now]);
	if (mxSon[now]<mxSon[rec]) rec=now;
}

void build(int now) {
	for (int i=G.ls[now];i;i=G[i].next) {
		if (del[G[i].y]) continue;
		rec=0; sum=size[G[i].y];
		get_root(G[i].y,now);
		fa[rec]=now; del[rec]=1;
		T.add_edge(now,rec,G[i].y);
		build(rec);
	}
}

void dfs(int now,int from) {
	mn[0][++cnt]=dis[now];
	pos[now]=cnt;
	for (int i=G.ls[now];i;i=G[i].next) {
		if (G[i].y==from) continue;
		dis[G[i].y]=dis[now]+G[i].w;
		dfs(G[i].y,now);
		mn[0][++cnt]=dis[now];
	}
}

int get_dis(int x,int y) {
	if (pos[x]>pos[y]) {
		x^=y; y^=x; x^=y;
	}
	int llgg=lg[pos[y]-pos[x]+1];
	return dis[x]+dis[y]-2*min(mn[llgg][pos[x]],mn[llgg][pos[y]-(1<<llgg)+1]);
}

void change(int x,LL v) {
	s1[x]+=v;
	for (int st=x;fa[x];x=fa[x]) {
		LL wjp=get_dis(st,fa[x])*v;
		s1[fa[x]]+=v;
		s2[fa[x]]+=wjp;
		s3[x]+=wjp;
	}
}

LL ask(int x) {
	LL res=s2[x];
	for (int st=x;fa[x];x=fa[x]) {
		res+=(s2[fa[x]]-s3[x])+(s1[fa[x]]-s1[x])*get_dis(st,fa[x]);
	}
	return res;
}

void solve(int now) {
	ans=ask(now);
	for (int i=T.ls[now];i;i=T[i].next) {
		LL lxf=ask(T[i].w);
		if (lxf<ans) {
			return solve(T[i].y);
		}
	}
}

int main(void) {
	lg[0]=-1; rep(i,1,N-1) lg[i]=lg[i/2]+1;
	int n=read(),m=read();
	rep(i,2,n) {
		int x=read(),y=read(),w=read();
		G.add_edge(x,y,w);
		G.add_edge(y,x,w);
	}
	dfs(1,0);
	rep(j,1,20) {
		if ((1<<j)>cnt) break;
		int len=1<<j-1;
		rep(i,1,cnt) if (i+(1<<j)-1<=cnt) {
			mn[j][i]=min(mn[j-1][i],mn[j-1][i+len]);
		}
	}
	rec=0; mxSon[0]=INF; sum=n;
	get_root(1,0); del[rec]=1;
	int root=rec;
	build(rec);
	for (int x,v;m--;) {
		x=read(),v=read();
		change(x,v);
		solve(root);
		printf("%lld\n", ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值