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

[ZJOI2015]幻想乡战略游戏

题意

有一棵大小为 \(n\) 的带权树, 每个点有一个权值, 权值可以修改 \(q\) 次,

找出一个补给点 \(x\) , 使得 \(\sum_{u \in V} val[u] \times dis(x,u)\) 最小, 并求出这个最小值.

一句话 : 求带权重心 (zsy说的)


附加条件 : 树中所有点的度数不超过 \(20\).

思路

一道你以为复杂度过不了, 但其实是过得了的题.

首先, 我们假定先选了一个点 \(u\) 作为补给点, 我们可以算出它的所有子节点 \(v\) 作为补给点时所需要的代价.

那么我们选取一个代价最小的点, 带权重心就一定在它的子树内;

假若点 \(u\) 的代价最小, 那么点 \(u\) 就是带权重心.


所以我们可以得到一个大致思路 :

先从一个点开始, 每次决定要往哪个子节点走, 然后在这个子节点的子树内递归处理这个子任务.


那么为了减小递归次数, 我们可以用动态点分治, 沿着点分树一层一层往下处理, 最后经过 \(\log n\) 层找到带权重心.

而我们在每次层中的操作就是 : 枚举当前点的每一个子节点, 找出最小的那个, 然后跳向它对应的重心.


那我们现在的问题就是 : 该如何计算一个点作为补给点时所需的代价.

首先, 有了动态点分治, 我们可以维护一个点作为补给点时, 它在 点分树中的子树 中的点到它的中代价, 设为 \(v1\),

那么, 设当前点为 \(u\) ,根据 \(ft[u]\) ( \(u\) 在点分树上的父亲) 的 \(v1\), 再加上 一些其他东西 , 我们就可以求出 \(ft[u]\) 在点分树中的子树的点到点 \(u\) 的总代价, 这样一层一层往上, 那我们就可以得到整棵树到点 \(u\) 的代价.

那, 这些 "其他东西" 是什么呢?

我们可以把 \(ft[u]\) 的子树分为两部分, (注 : 从这里开始, "子树" 均指 点分树上的子树.)

  1. 在点 \(u\) 的子树中的部分.
  2. 不在点 \(u\) 的子树中的部分.

第一部分所需的代价就是 \(v1[u]\),

为了计算第二部分的代价, 我们增添几个值,

  1. \(v2[u]\) : 表示点 \(u\) 子树中的点到 \(ft[u]\) 所需的代价.
  2. \(s2[u]\) : 表示点 \(u\) 子树中的点的总权值.

那么第二部分的代价就为 \(v1[ft[u]]-v2[u]+dis(u,ft[u])*(s2[fa]-s2[u])\).

可以这样理解 :

\(v1[ft[u]]-v2[u]\) 表示将点 \(u\) 的子树的代价去除.

\(+dis(u,ft[u])*(s2[fa]-s2[u])\) 表示将到 \(ft[u]\) 的代价转为到 \(u\) 的代价.


由上可得, 求一个点的代价的时间复杂度为 : 在点分树上逐层向上的 \(\log n\) 乘上算 \(dis\) 的 \(\log n\), 总共为 \(O(\log^2 n)\).

再乘上之前的复杂度, 得到一次询问的复杂度为 \(O(20\log^3 n)\),

那么总复杂度为 \(O(20n\log^3 n)\) (因为 \(q\) 和 \(n\) 是相同规模的), 大概有 \(10^9\), 看起来就很不靠谱, 但是如果把用树剖求 \(lca\), 这个复杂度就跑不满, 再加上 \(6s\) 的时限, 还是可以过的, 并且洛谷上最优解总时间只有 \(500\) 多毫秒....

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int _=1e5+7;
const int __=2e5+7;
const int L=20;
const int inf=0x3f3f3f3f;
bool be;
int n,q,dep[_],sz[_],top[_],son[_],fat[_],rt,minx,ft[_],rtt[__];   // v1: to self  v2: to father
ll v1[_],v2[_],s2[_],dis[_];
int lst[_],nxt[__],to[__],tot;
ll len[__];
bool vis[_];
bool en;
void add(int x,int y,ll w){ nxt[++tot]=lst[x]; to[tot]=y; len[tot]=w; lst[x]=tot; }
void dfs1(int u,int fa){
  fat[u]=fa;
  dep[u]=dep[fa]+1;
  for(int i=lst[u];i;i=nxt[i]){
    int v=to[i];
    if(v==fa) continue;
    dis[v]=dis[u]+len[i];
    dfs1(v,u);
    sz[u]+=sz[v];
    if(sz[v]>sz[son[u]]) son[u]=v;
  }
}
void dfs2(int u){
  if(son[u]){
    top[son[u]]=top[u];
    dfs2(son[u]);
  }
  for(int i=lst[u];i;i=nxt[i])
    if(!top[to[i]]){
      top[to[i]]=to[i];
      dfs2(to[i]);
    }
}
int Lca(int x,int y){
  while(top[x]!=top[y]){
    if(dep[top[x]]<dep[top[y]]) swap(x,y);
    x=fat[top[x]];
  }
  return dep[x]<=dep[y] ?x :y;
}
ll dist(int x,int y){ return dis[x]+dis[y]-2*dis[Lca(x,y)]; }
void g_rt(int u,int fa,int sum){
  int maxn=0; sz[u]=1;
  for(int i=lst[u];i;i=nxt[i]){
    int v=to[i];
    if(vis[v]||v==fa) continue;
    g_rt(v,u,sum);
    sz[u]+=sz[v];
    maxn=max(maxn,sz[v]);
  }
  maxn=max(maxn,sum-sz[u]);
  if(maxn<minx){ minx=maxn; rt=u; }
}
void init(int u,int lrt,int sum){
  minx=inf;
  g_rt(u,0,sz[u]<sz[lrt] ?sz[u] :sum-sz[lrt]);
  sum=sz[u];
  ft[rt]=lrt;
  vis[rt]=1; u=rt;
  for(int i=lst[u];i;i=nxt[i])
    if(!vis[to[i]]){
      init(to[i],u,sum);
      rtt[i]=rt;
    }
  rt=u;
  vis[rt]=0;
}
void modify(int x,ll w){
  int u=x,fa=ft[u];
  ll len;
  while(fa){
    len=dist(fa,x);
    //printf("u: %d fa: %d len: %d\n",u,fa,len);
    v1[fa]+=w*len;
    v2[u]+=w*len;
    s2[u]+=w;
    u=ft[u]; fa=ft[u];
  }
  s2[u]+=w;
}
ll cst(int x){
  int u=x,fa=ft[u];
  ll len,res=v1[x];
  while(fa){
    len=dist(x,fa);
    res+=v1[fa]-v2[u]+len*(s2[fa]-s2[u]);
    u=ft[u]; fa=ft[u];
  }
  return res;
}
ll query(){
  int u=rt;
  ll ans=1e17;
  while(u){
    ans=min(ans,cst(u));
    int x=0;
    ll minx=1e17;
    for(int i=lst[u];i;i=nxt[i]){
      if(!rtt[i]) continue;
      int v=to[i];
      ll tmp=cst(v);
      if(tmp<minx){ minx=tmp; x=rtt[i]; }
    }
    u=x;
  }
  return ans;
}
int main(){
  //freopen("x.in","r",stdin);
  //freopen("x.out","w",stdout);
  cin>>n>>q;
  int x,y; ll w;
  for(int i=1;i<n;i++){
    scanf("%d%d%lld",&x,&y,&w);
    add(x,y,w);
    add(y,x,w);
  }
  init(1,0,n);
  dfs1(1,0);
  top[1]=1;
  dfs2(1);
  for(int i=1;i<=q;i++){
    scanf("%d%lld",&x,&w);
    modify(x,w);
    printf("%lld\n",query());
  }
}

原文地址:https://www.cnblogs.com/brucew-07/p/12130377.html

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

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

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

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

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

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

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

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

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

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

[ZJOI2015]幻想乡战略游戏 - 动态点分治

先考虑无修要怎么操作. 发现在无修的情况下,我们可以用一个换根\(dp\)解决. 那么带修改的情况要怎么办呢? 每次修改重新\(dp\)一遍不就行了(雾. 好的,让我们先来敲一个\(O(N^2)\)的\(dp\). #include <bits/stdc++.h> using namespace std; typedef long long ll; inline ll ty() { char ch = getchar(); ll x = 0, f = 1; while (ch < '0'

[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

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序