luogu P3345 [ZJOI2015]幻想乡战略游戏(点分树)

题意自己看。。。

思路

没想到今(昨)天刷着刷着点分治的水题,就刷出来了一个点分树。。。

然后就疯狂地找题解,代码,最后终于把它给弄懂了。

点分树——动态点分治,对于此题来说,我们发现设u为当前的补给站位置,v是它的一个儿子。同时设dis(i,j)为树上i点到j点的距离。sumi为以i为跟的子树中d(也就是军队数)的总量

我们把补给站从u转移到v,答案的变化为dis(u,v)*(sumu-sumv)-dis(u,v)*sumv=dis(u,v)*(sumu-2*sumv)

所以当2*sumv>sumu把补给站转移v会使答案变优,一直这样操作下去,直到不管如何转移都无法使答案变优,当前点就是答案。

我们对于每一次修改都维护一下sum然后默认根为补给站,然后像刚才一样转移补给站,就有了这个题的大体思路。

但这样肯定会爆炸啊(每次只会转移一步,复杂度和深度有关)

所以我们就可以用到点分树了。

我们利用点分治的方法(找重心作为根节点,然后递归处理子树)重新构建一颗树,称为分治树。

举个例子,比如说样例

(左为原树右为分治树)

这样我们就得到一颗深度为log的树。

这样我们转移就不会因为深度太大而GG了。

但是,这样一来的话因为树的结构不一样了,补给站的转移就会很难。因为一个点的儿子在原树中不一定和它直接相连(之后我们的补给站的转移都是在分治树上进行的)

但还是有办法解决的。我们记录这这些东西。dis(i,j)为i,j在原树的距离。cnt[i]为分治树中以i为根的子树中有多少个d,sumdi为分治树中以i为根的子树中所有的点j的dis(i,j)*dj之和

最后一个答案数组sumi,这个数组比较玄学,建议结合代码理解,当i为当前选定补给站时suni是所有的dx*dis(x,i)

这个东西很好维护,我们预处理出每一个点在分治树中的每一个祖先,修改时,一个一个跳祖先就行了。

我们现在讨论从u转移到它分治树中的一个子节点v,v是原来这颗子树中的重心但在原树中并不一定和u之间相连,我们设原树中直接和u相连的点是w

只要把v所在子树之外的所有信息扔到w中然后递归就可以了。那怎么维护w的信息呢?

我们记录在v处记录下w和w与u这条边的权值,然后我们在cntw处加上cnt[u]-cnt[v],然后在sumw处加上sum[u]-sumd[v]+(cnt[u]-cnt[v])*c;

然后我们在维护的时候在w分治树的祖先的sum都加上sumw,最后答案就取sum[当前点]就行了。

  1 #include<iostream>
  2 #include<cstring>
  3 #include<cstdio>
  4 #include<vector>
  5 #include<cmath>
  6 #include<algorithm>
  7 using namespace std;
  8 #define ll long long
  9 typedef pair<ll,ll>p1;
 10 typedef pair<ll,p1>p2;
 11 const long long N=100100;
 12 vector<p1> pre[N];//存分治树祖先的编号,和原树中到祖先的距离
 13 vector<p2> ch[N];//存与分治树的儿子,同一子树中的原树中的儿子,和连接原树中的儿子的边权
 14 vector<p2> record;//回溯用
 15 vector<ll> sumdist[N];//存分治树中的儿子的sum
 16 long long tot,head[N];
 17 long long size[N],g[N],vis[N],all,realroot,root,n,m;
 18 long long sum[N],cnt[N];
 19 struct edge{
 20     long long to,nxt,w;
 21 }e[N<<1];
 22 void add(long long u,long long v,long long w){
 23     tot++;
 24     e[tot].nxt=head[u];
 25     e[tot].to=v;
 26     e[tot].w=w;
 27     head[u]=tot;
 28 }
 29 void get_root(long long u,long long fa){
 30     size[u]=1;
 31     g[u]=0;
 32     for(long long i=head[u];i;i=e[i].nxt){
 33         long long v=e[i].to;
 34         if(vis[v]==1||v==fa)continue;
 35         get_root(v,u);
 36         size[u]+=size[v];
 37         g[u]=max(g[u],size[v]);
 38     }
 39     g[u]=max(g[u],all-size[u]);
 40     if(g[u]<g[root])root=u;
 41 }
 42 void get_dis(long long u,long long fa,long long top,long long w){
 43     pre[u].push_back(p1(top,w));
 44     size[u]=1;
 45     for(long long i=head[u];i;i=e[i].nxt){
 46         long long v=e[i].to;
 47         if(vis[v]==1||v==fa)continue;
 48         get_dis(v,u,top,w+e[i].w);
 49         size[u]+=size[v];
 50     }
 51 }
 52 void work(long long u){
 53     vis[u]=1;
 54     pre[u].push_back(p1(u,0));
 55     for(long long i=head[u];i;i=e[i].nxt){
 56         long long v=e[i].to;
 57         if(vis[v]==1)continue;
 58         get_dis(v,0,u,e[i].w);
 59         root=0;all=size[v];
 60         get_root(v,0);
 61         ch[u].push_back(p2(root,p1(v,e[i].w)));
 62         work(root);
 63     }
 64 }
 65 void update(long long x,ll c,ll cc){
 66     for(long long i=0;i<pre[x].size();++i){
 67         long long u=pre[x][i].first;
 68         cnt[u]+=c;
 69         sum[u]+=cc+c*pre[x][i].second;
 70         if(i!=pre[x].size()-1){
 71             for(long long j=0;j<ch[u].size();j++)
 72                 if(ch[u][j].first==pre[x][i+1].first)sumdist[u][j]+=cc+c*pre[x][i].second;
 73         }
 74     }
 75 }
 76 ll query(){
 77     long long x=realroot;
 78     long long mx;
 79     record.clear();
 80     while(x){
 81         mx=0;
 82         for(long long i=1;i<ch[x].size();i++){
 83             if(cnt[ch[x][i].first]>cnt[ch[x][mx].first])mx=i;
 84         }
 85         if(ch[x].size()==0||cnt[ch[x][mx].first]*2<=cnt[x]){
 86             ll ans=sum[x];
 87             for(long long i=0;i<record.size();i++){
 88                 update(record[i].first,record[i].second.first,record[i].second.second);
 89             }
 90             return ans;
 91         }
 92         long long v=ch[x][mx].first;
 93         record.push_back(p2(ch[x][mx].second.first,p1(-(cnt[x]-cnt[v]),-(sum[x]-sumdist[x][mx]+(cnt[x]-cnt[v])*ch[x][mx].second.second))));
 94         update(ch[x][mx].second.first,cnt[x]-cnt[v],sum[x]-sumdist[x][mx]+(cnt[x]-cnt[v])*ch[x][mx].second.second);
 95         x=v;
 96     }
 97 }
 98 int main(){
 99     scanf("%lld%lld",&n,&m);
100     for(long long i=1;i<=n-1;++i){
101         long long u,v,w;
102         scanf("%lld%lld%lld",&u,&v,&w);
103         add(u,v,w);
104         add(v,u,w);
105     }
106     g[0]=n+10;root=0;all=n;
107     get_root(1,0);realroot=root;
108     work(root);
109     for(long long i=1;i<=n;++i){
110         sumdist[i]=vector<ll>(ch[i].size(),0);
111     }
112     while(m--){
113         long long x,y;
114         scanf("%lld%lld",&x,&y);
115         update(x,y,0);
116         printf("%lld\n",query());
117     }
118     return 0;
119 }

原文地址:https://www.cnblogs.com/Xu-daxia/p/9630536.html

时间: 2024-11-07 22:33:11

luogu P3345 [ZJOI2015]幻想乡战略游戏(点分树)的相关文章

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

[BZOJ3924][Zjoi2015]幻想乡战略游戏 Description 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打仗了. 在打仗之前,幽香现在面临一个非常基本的管理问题需要解决. 整个地图是一个树结构,一共有n块空地,这些空地被n-1条带权边连接起来,使得每两个点之间有一条唯一的路径将它们连接起来.在游戏中,幽香可能在空地上增加或者减少一些军

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

题目描述 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打仗了. 在打仗之前,幽香现在面临一个非常基本的管理问题需要解决. 整个地图是一个树结构,一共有n块空地,这些空地被n-1条带权边连接起来,使得每两个点之间有一条唯一的路径将它们连接起来. 在游戏中,幽香可能在空地上增加或者减少一些军队.同时,幽香可以在一个空地上放置一个补给站. 如果补给站在点u上,

P3345 [ZJOI2015]幻想乡战略游戏

传送门 考虑先随便找一个点作为根,然后再慢慢移动根,这样一步步走到最优的点 设 $sum[x]$ 表示节点 $x$ 的子树的军队数,$len(x,y)$ 表示 $x,y$ 之间边的长度 那么对于根节点 $x$ 的一个儿子 $v$,考虑把儿子搞为根时,代价的改变量 $v$ 的子树内的军队消耗减少,共减少了 $sum[v]\cdot len(x,v)$ $v$ 的子树外的军队消耗增加,即根节点 $x$ 的子树内除了 $v$ 子树的军队消耗增加 代价增加了 $(sum[x]-sum[v])\cdot

[ZJOI2015]幻想乡战略游戏 解题报告 (动态点分治)

[ZJOI2015]幻想乡战略游戏 题意 有一棵大小为 \(n\) 的带权树, 每个点有一个权值, 权值可以修改 \(q\) 次, 找出一个补给点 \(x\) , 使得 \(\sum_{u \in V} val[u] \times dis(x,u)\) 最小, 并求出这个最小值. 一句话 : 求带权重心 (zsy说的) 附加条件 : 树中所有点的度数不超过 \(20\). 思路 一道你以为复杂度过不了, 但其实是过得了的题. 首先, 我们假定先选了一个点 \(u\) 作为补给点, 我们可以算出它

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

题目描述 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打仗了. 在打仗之前,幽香现在面临一个非常基本的管理问题需要解决. 整个地图是一个树结构,一共有n块空地,这些空地被n-1条带权边连接起来,使得每两个点之间有一条唯一的路径将它们连接起来.在游戏中,幽香可能在空地上增加或者减少一些军队.同时,幽香可以在一个空地上放置一个补给站. 如果补给站在点u上,并

BZOJ 3924 Zjoi2015 幻想乡战略游戏 动态树分治

题目大意:给定一棵树,每个点有一个点权,多次改变某个点的点权,多次查询带权重心到所有点的带权距离之和 此生无悔入东方,来世愿生幻想乡 首先我们考虑如何计算一个点到所有点的带权距离之和且支持修改 用动态树分治就好了嘛... 每个点记录子树中带权距离之和,以及权值之和,再在每个子树中记录一个需要减掉的版本 然后一直向上扫到根就能统计了 ↑这段话面对会写动态树分治的人,不会的先去切捉迷藏吧 然后就好搞了... 对于分治结构的每一个点,我们枚举它的出边 如果某条出边连向的点的距离之和小于当前点,那么答案

[Luogu3345][ZJOI2015]幻想乡战略游戏

Luogu 题意: 动态维护带权重心. sol 这是一道写起来很舒服的动态点分治.(不像某些毒瘤题) 我们考虑,如果你选择的补给点不是当前的带权重心,那么带权重心就在补给点的一个子树中(你把补给点当做根的话).那么,你把补给点向带权重心所在的子树中移动的时候,答案一定会减小.换言之,如果补给点无论向哪个方向移动答案都不会减小,那么这个点就是带权重心. 所以我们每次考虑移动补给点然后计算即可. 但是怎么移动呢? 最优复杂度的移动策略是:先假设点分树的根就是补给,然后你一次检查与它在原树中相连的所有

BZOJ 3924: [Zjoi2015]幻想乡战略游戏(动态点分治)

这种动态点分治嘛,GDKOI时听打到了,也有同学讲到了,所以印象比较深刻也就想出来了,然后就在实现方面卡了好久= = 不得不说CLJ说得真的太简单了,实现方面根本没提. 首先我们可以先用树分治构建出这棵树的分治树,也就是把这棵树的重心作为根节点然后子树为他的子树的重心这样递归下去,然后每个节点存的是其子树的信息. 对于每个节点我们保存这个子树的dv的总和已经把该节点作为点的答案值 这样对于修改能在log n的时间内解决 寻找答案的时候,我们可以发现,如果现在节点的子树dv和*2大于总节点,那么向

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

对于一个点,要求出它到所有点的带权距离和,只需记录下树分治的结构然后查询即可. 修改$O(\log n)$,查询$O(\log n)$. 到所有点带权距离和最小的点显然是这棵树的带权重心. 以1号点为根,考虑一条从父亲x到孩子y的边: 若y子树内权值和>=总权值和-y子树内权值和,即2*y子树内权值和>=总权值和,则重心在y的子树里,否则不在. 所以重心一定是深度最大的满足2*子树内权值和>=总权值和的点. 可以发现重心及其祖先都满足2*子树内权值和>=总权值和,而这些点在DFS序