幻想乡的策略游戏

时间:2023-01-05 08:03:39

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版试题:JudgeOnline/upload/201708/zjoi2015d1.pdf

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

Output

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

Sample Input

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

Sample Output

0
1
4
5
6

HINT

第一道动态点分治的题,其实以前做过但是完全忘了,现在重新做一遍又有新的理解

首先这个题意是树上点权修改,询问带权重心。我们设$sum_u$表示$u$子树内的权值和,$sum_v$表示$v$子树内的权值和,如果要从$u$走到$v$,答案的变化量为$len(u,v)*(sum_u-sum_v-sum_v)$

即当$sum_u \le 2*sum_v$时,我们才能从$u$走到$v$,显然$v$点只会有一个,而且这个过程一定会停止

而如何快速求出每个点为根的花费以及如何快速支持修改,这里就可以考虑构建点分树了

我们知道点分治有一个性质,即原树上两点$p$和$q$两点之间的路径的第一个重心就是点分树上的$LCA$

这样我们爬一遍树,就可以求出$p$与其他点之间的信息了,每次修改也暴力爬树即可

代码:

  1 #include<iostream>
  2 #include<cstdio>
  3 #define M 400010
  4 #define ll long long
  5 using namespace std;
  6 struct point{int next,to,dis;}e[M<<1];
  7 //sumv[i]表示以i为根的子树权值和
  8 //ans[i]表示以i为根的子树中所有节点到i结点dv*dis的和
  9 //ans1[i]表示点分树中以i为根的子树中所有结点到i的父亲的dv*dis的和 
 10 int n,m,num,cnt,last,sum,root;
 11 int head[M],deep[M],size[M],maxn[M];
 12 int fa[M],top[M],sz[M],f[M],son[M];
 13 ll sumv[M],ans1[M],ans[M],dist[M];
 14 bool vis[M];
 15 void add(int from,int to,int dis)
 16 {
 17     e[++num].next=head[from];
 18     e[num].to=to;
 19     e[num].dis=dis;
 20     head[from]=num;
 21 }
 22 void dfs(int x)
 23 {
 24     sz[x]=1;
 25     for(int i=head[x];i;i=e[i].next)
 26     {
 27         int to=e[i].to;
 28         if(sz[to]) continue;
 29         deep[to]=deep[x]+1;
 30         dist[to]=dist[x]+e[i].dis;
 31         f[to]=x;
 32         dfs(to);
 33         sz[x]+=sz[to];
 34         if(sz[son[x]]<sz[to]) son[x]=to;
 35     }
 36 }
 37 void dfs2(int x,int tp)
 38 {
 39     top[x]=tp;
 40     if(son[x]) dfs2(son[x],tp);
 41     for(int i=head[x];i;i=e[i].next)
 42     {
 43         int to=e[i].to;
 44         if(to==f[x]||to==son[x]) continue;
 45         dfs2(to,to);
 46     }
 47 }
 48 int lca(int x,int y)
 49 {
 50     while(top[x]!=top[y])
 51     {
 52         if(deep[top[x]]<deep[top[y]]) swap(x,y);
 53         x=f[top[x]];
 54     }
 55     if(deep[x]<deep[y]) return x;
 56     else return y;
 57 }
 58 int dis(int x,int y)
 59 {
 60     int LCA=lca(x,y);
 61     return dist[x]+dist[y]-2*dist[LCA];
 62 }
 63 void getroot(int x,int fa)
 64 {
 65     size[x]=1; maxn[x]=0;
 66     for(int i=head[x];i;i=e[i].next)
 67     {
 68         int to=e[i].to;
 69         if(to==fa||vis[to]) continue;
 70         getroot(to,x);
 71         size[x]+=size[to];
 72         maxn[x]=max(maxn[x],size[to]);
 73     }
 74     maxn[x]=max(maxn[x],sum-size[x]);
 75     if(maxn[x]<maxn[root]) root=x;
 76 }
 77 void work(int x)
 78 {
 79     vis[x]=true;
 80     for(int i=head[x];i;i=e[i].next)
 81     {
 82         int to=e[i].to;
 83         if(vis[to]) continue;
 84         root=0,sum=size[to];
 85         getroot(to,0); fa[root]=x;
 86         work(root);
 87     }
 88 }
 89 void update(int x,int val)
 90 {
 91     sumv[x]+=val;
 92     for(int i=x;fa[i];i=fa[i])
 93     {
 94         ll d=dis(fa[i],x);
 95         sumv[fa[i]]+=val;
 96         ans1[i]+=val*d; 
 97         ans[fa[i]]+=val*d;
 98     }
 99 }
100 ll cal(int x)
101 {
102     ll ret=ans[x];
103     for(int i=x;fa[i];i=fa[i])
104     {
105         ll dt=dis(fa[i],x);
106         ret+=(ans[fa[i]]-ans1[i]);//去掉子树i 
107         ret+=dt*(sumv[fa[i]]-sumv[i]);//重新计算子树i的贡献 
108     }
109     return ret;
110 }
111 ll ask(int x)
112 {
113     ll K=cal(x);
114     for(int i=head[x];i;i=e[i].next)
115     {
116         int to=e[i].to;
117         ll tmp=cal(to);
118         if(tmp<K) return ask(to);
119     }
120     last=x; return K;
121 }
122 int main()
123 {
124     scanf("%d%d",&n,&m);
125     for(int i=1;i<=n-1;i++)
126     {
127         int x,y,z;
128         scanf("%d%d%d",&x,&y,&z);
129         add(x,y,z); add(y,x,z);
130     }
131     deep[1]=1; dfs(1); dfs2(1,1);
132     sum=maxn[0]=n;
133     getroot(1,0);
134     work(root);
135     last=1;
136     for(int i=1;i<=m;i++)
137     {
138         int x,y;
139         scanf("%d%d",&x,&y);
140         update(x,y);
141         printf("%lld\n",ask(last));
142     }
143     return 0;
144 }