【BZOJ 3924】[Zjoi2015]幻想乡战略游戏

题目:

  

题解:

  对点分树理解加深了233,膜拜zzh干翻紫荆花。

  感谢zzh的讲解。

  首先优化基于传统DP,假设树不发生变化,我们就可以利用DP求出带权重心。

  考虑修改,我们思路不变,还是从root开始找,但发现这样会被卡成$n^2$,原因是每次经过点太多,为了优化,考虑点分树,由于点分树的性质使得假设我们可以在点分树上找到最优解,那么每次最多经过$log$个节点,可以保证时间复杂度。

  然后考虑在点分树转移,假设当前节点为x,我们枚举其在原树中的边,假设当前枚举边的另一端为y,那么由DP可以得出如果以当前边分为两半,若y的一半点权和大于所有点权的一半,那么最优解一定在y那边存在,然后我们由点分树直接跳跃到y对应的块中。若不存在这样的y,则x一定为最优解。

  这样的话我们的目的就是求x点对应的答案以及y一边对应的点权和,我们用三个数组来记录当前x的点分子树的点权和,点分子树到达x的$d*dis$和,以及到达其父亲的$d*dis$和,这样统计x的答案就可以在$log$的时间内完成。

  对于y我们可以开一个$log$大小的数组来记录在点分数上走过的点,并按照原树dfs排序,每次到达一个新的x用$log$更新,并在此序列上维护一个$sum$表示经过路径上x与其儿子s点权和之差,那么考虑若枚举的y是x在原树的儿子或父亲时的情况,分类讨论,利用$sum$快速求出y一边的点权和。

  综上所述,时间复杂度为$O(20nlog_2^nlog_2^{log_2^n})$

代码:

  1 #define Troy
  2 #define inf 0x7fffffff
  3
  4 #include "bits/stdc++.h"
  5
  6 using namespace std;
  7
  8 inline int read(){
  9     int s=0,k=1;char ch=getchar();
 10     while (ch<‘0‘|ch>‘9‘)    ch==‘-‘?k=-1:0,ch=getchar();
 11     while (ch>47&ch<=‘9‘)    s=s*10+(ch^48),ch=getchar();
 12     return s*k;
 13 }
 14
 15 const int N=1e5+5;
 16
 17 typedef long long ll;
 18
 19 struct edges {
 20     int v,nv,w;edges *last;
 21 }edge[N<<1],*head[N];int cnt;
 22
 23 inline void push(int u,int v,int w){
 24     edge[++cnt]=(edges){v,0,w,head[u]};head[u]=edge+cnt;
 25 }
 26
 27 int bit[30];
 28
 29 class ST{
 30 public:
 31     inline void build(int *a,int n){
 32         lgs[0]=-1;
 33         register int i,j;
 34         for (i=1;i<=n;++i)  lgs[i]=lgs[i>>1]+1,f[i][0]=a[i];
 35         for (i=1;bit[i]<=n;++i)
 36             for (j=1;j+bit[i]<=n+1;++j)
 37                 f[j][i]=min(f[j][i-1],f[j+bit[i-1]][i-1]);
 38     }
 39
 40     inline int query(int l,int r){
 41         if(r<l) swap(l,r);int t=lgs[r-l+1];
 42         return min(f[l][t],f[r-bit[t]+1][t]);
 43     }
 44 private:
 45     int f[N<<1][20],lgs[N<<1];
 46 }RMQ;
 47
 48 int dis[N],eular[N<<1],num,beg[N],End[N],n,m;
 49
 50 inline void DFS(int x,int fa){
 51     eular[beg[x]=++num]=dis[x];
 52     for(edges *i=head[x];i;i=i->last)   if(i->v^fa){
 53         dis[i->v]=dis[x]+i->w;
 54         DFS(i->v,x);
 55         eular[++num]=dis[x];
 56     }End[x]=num;
 57 }
 58
 59 inline ll get_dis(int x,int y){
 60     return dis[x]+dis[y]-(RMQ.query(beg[x],beg[y])<<1);
 61 }
 62
 63 class Point_Divide_Tree{
 64 public:
 65     int root,tot,fat[N],size[N],heavy[N],dsum[N];
 66     bool vis[N];
 67     ll dissum[N],fdissum[N];
 68
 69     inline void dfs(int x,int fa){
 70         size[x]=1,heavy[x]=0;
 71         for(edges *i=head[x];i;i=i->last)   if(i->v!=fa&&!vis[i->v]){
 72             dfs(i->v,x),size[x]+=size[i->v];
 73             heavy[x]=max(heavy[x],size[i->v]);
 74         }heavy[x]=max(heavy[x],tot-size[x]);
 75         if(heavy[root]>heavy[x])    root=x;
 76     }
 77
 78     inline void build(int x,int fa){
 79         root=0,dfs(x,0);
 80         vis[x=root]=true,fat[x]=fa,dfs(x,x);
 81         for (edges *i=head[x];i;i=i->last) if(!vis[i->v]){
 82             tot=size[i->v];
 83             build(i->v,x),i->nv=root;
 84         }root=x;
 85     }
 86
 87     inline void insert(int x,int y,int val){
 88         tot+=val;
 89         while(x){
 90             dsum[x]+=val;
 91             dissum[x]+=get_dis(x,y)*val;
 92             if(fat[x])
 93                 fdissum[x]+=get_dis(fat[x],y)*val;
 94             x=fat[x];
 95         }
 96     }
 97
 98     ll ans,sum[30];
 99     int pos[30],leth;
100
101     inline int calc(int fa,int x,int real){
102         int ret=dsum[real];
103         if(dis[x]<dis[fa]){
104             int l=lower_bound(pos+1,pos+leth+1,beg[fa])-pos,
105                 r=upper_bound(pos+1,pos+leth+1,End[fa])-pos-1;
106             ret+=sum[leth]-sum[r]+sum[l-1];
107         }else{
108             int l=lower_bound(pos+1,pos+leth+1,beg[x])-pos,
109                 r=upper_bound(pos+1,pos+leth+1,End[x])-pos-1;
110             ret+=sum[r]-sum[l-1];
111         }
112         return ret;
113     }
114
115     inline ll calc(int x){
116         ll ret=dissum[x];
117         int p=x;
118         while(fat[x]){
119             ret+=(dsum[fat[x]]-dsum[x])*get_dis(fat[x],p)+dissum[fat[x]]-fdissum[x];
120             x=fat[x];
121         }return ret;
122     }
123
124     inline void update(int x,int y){
125         ll now=dsum[x]-dsum[y];
126         for(int i=leth+1;i;--i){
127             sum[i]=sum[i-1]+now;
128             if(i==1||pos[i-1]<=beg[x]){
129                 pos[i]=beg[x];
130                 break;
131             }else   pos[i]=pos[i-1];
132         }++leth;
133     }
134
135     inline void query(int x){
136         for(edges *i=head[x];i;i=i->last) if(i->nv){
137             if(calc(x,i->v,i->nv)*2>=tot){
138                 update(x,i->nv);
139                 ans=calc(i->nv);
140                 query(i->nv);
141                 break;
142             }
143         }
144     }
145
146     inline void query(){
147         leth=0;
148         ans=dissum[root];
149         query(root);
150         printf("%lld\n",ans);
151     }
152 }tree;
153
154 int main(){
155     register int i,j;
156     for (i=0;i<=20;++i)  bit[i]=1<<i;
157     n=read(),m=read();
158     for (i=1;i^n;++i){
159         int a=read(),b=read(),c=read();
160         push(a,b,c),push(b,a,c);
161     }
162     DFS(1,1),RMQ.build(eular,num);
163     tree.tot=n,tree.heavy[0]=inf;
164     tree.build(1,0),tree.tot=0;
165     while(m--){
166         i=read(),j=read();
167         tree.insert(i,i,j);
168         tree.query();
169     }
170 }
时间: 2024-10-07 11:58:02

【BZOJ 3924】[Zjoi2015]幻想乡战略游戏的相关文章

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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>s