首页 > 其他分享 >luogu P3345 [ZJOI2015]幻想乡战略游戏

luogu P3345 [ZJOI2015]幻想乡战略游戏

时间:2023-05-10 10:03:07浏览次数:63  
标签:rt sz val int luogu ZJOI2015 dis P3345 define

P3345 [ZJOI2015]幻想乡战略游戏

这道题还是比较有意思的,做了一个比较长的时间,但是点分树实在是太毒瘤了,所以记录一下线段树的做法。

题面

给一棵树,有边权,每次修改一个点的点权,修改完后输出所有点到这棵树的带权重心的贡献,即\(\sum dis_i\times val_i\)

题解

考虑朴素的暴力找重心,直接在无权重心的基础上将树的大小改为树的权值大小就可以了,正确性还是比较显然的。
想一想怎么优化寻找重心的这一个过程。
我们会发现一个神奇的东西,一个点越接近重心,它的答案就越小
这有什么用呢?这样我们可以像移动指针一样移动根,每移动一步答案都会减少,一直移动到不能移动的点就是重心了。
可以移动到一个点\(x\)当且仅当\(sz_{rt} \leq sz_x\times 2\),这里的\(sz_x\)是子树的点权和。
怎么证明呢?
从一个点\(x\)走到点\(y\),答案的变化为\(e_{x,y}\times(sz_{rt}-sz_y-sz_y)\)。
要想答案缩小,那么就必须满足\(sz_{rt} \leq sz_x\times 2\)。
怎么做有什么好处呢?一个点是否走只和rt有关系。
那这样我们就可以支持快速移动了,要找的就是满足\(sz_{rt} \leq sz_x\times 2\)且不能移动的点。
我们要怎么找呢?接下来就是一个很妙的操作了。
一个点可以移动是既可以移动到子树外也可以移动到子树内的,这对于我们的维护来说是很麻烦的。
我们直接以\(1\)为初始根,这样因为答案的修改有单调性,只可能移动到子树内,而不可能移动到子树之外。
接下来问题就转化成了我们要寻找满足\(sz_{rt} \leq sz_x\times 2\)且深度最深的点。
这是一个很\(easy\)的操作,只需要在线段树上维护一个最大值,在满足条件的区间上贪心地往右走就行了。
关于修改,只需要在修改时将到\(1\)的路径上全部一起修改了即可。
这样我们就完成了找到重心的操作就做完了,这下考虑怎么统计答案
设当前重心为\(x\),发现这其实是一个点对的贡献,即\(\sum dis(x,y)*val_y\)。
画个图,发现换根之后\(x\)的子树距离不变,\(x\)之上的要删去\(lca(x,y)\)的贡献然后再连接到\(x\)上,这样统计答案其实是一件很困难的事情。
我们不妨换一个思路,根据我们一般做树上差分时会用到这样一个式子\(dis(x,y)=dis(x,rt)+dis(y,rt)-2\times dis(lca,rt)\)。
把它套到这题来\(dis(x,rt)\times val_y+dis(y,rt)\times val_y-2\times dis(lca_{x,y},rt)\times val_y\)

这个式子的前两项是容易的,关于第三项,我们会发现实际上只有\(x\)在\(rt\)所在的子树会产生一个减的贡献,跨越\(rt\)的子树的\(lca\)都是\(rt\),是无意义的。
那么我们我们只需要维护子树的权值与一条边边权之积,也就是在线段树上维护\(sz_i\times w_i,w_i\)是边权。
因为\(x\)所在\(rt\)的子树内的点到\(x\)的\(lca\),都是\(x\)到\(rt\)路径上的点,我们就直接遍历\(lca\)统计答案。
统计\(x\)到路径上的节点的\(\sum w_i\times sz_i\),这样对于子树内每一个点都都统计了\(dis_{lca_{x,y},rt}\)的贡献,正确性可以分两类讨论一下就好了,这里略。

code

#include <bits/stdc++.h>
#define int long long
#define rg register
#define pc putchar
#define gc getchar
#define pf printf
#define space pc(' ')
#define enter pc('\n')
#define me(x,y) memset(x,y,sizeof(x))
#define pb push_back
#define FOR(i,k,t,p) for(rg int i(k) ; i <= t ; i += p)
#define ROF(i,k,t,p) for(rg int i(k) ; i >= t ; i -= p)
using namespace std ;
bool s_gnd ;
inline void read(){}
template<typename T,typename ...T_>
inline void read(T &x,T_&...p)
{
    x = 0 ;rg int f(0) ; rg char c(gc()) ;
    while(!isdigit(c)) f |= (c=='-'),c = gc() ;
    while(isdigit(c)) x = (x<<1)+(x<<3)+(c^48),c = gc() ;
    x = (f?-x:x) ;
    read(p...);
}
int stk[30],tp ;
inline void print(){}
template<typename T,typename ...T_>
inline void print(T x,T_...p)
{
    if(x < 0) pc('-'),x = -x ;
    do stk[++tp] = x%10,x /= 10 ; while(x) ;
    while(tp) pc(stk[tp--]^48) ; space ;
    print(p...) ;
}
const int N = 2e5+5 ;
int n,q,top,sum1,sum2 ;
int dep[N],fa[N],dis[N],wh[N] ;
int sz[N],vis[N],eal[N],son[N],pe[N],id[N] ;
struct Edge{int v,w ;} ;
struct Node{int l,r,f,val,w,si ;}tr[N<<2] ;
vector<Edge>e[N] ;
bool S_GND ;
void Dfs1(int x)
{
    vis[x] = sz[x] = 1 ;
    for(auto [v,w]:e[x]) if(!vis[v])
    {
        dis[v] = dis[x]+w,Dfs1(v),sz[x] += sz[v] ;
        if(sz[v] > sz[son[x]]) son[x] = v ; eal[v] = w,fa[v] = x ;
    }
}
void Dfs2(int x,int tops)
{
    vis[x] = 1,id[x] = ++top,wh[top] = x,pe[x] = tops ;
    if(!son[x]) return ; Dfs2(son[x],tops) ;
    for(auto [v,w]:e[x]) if(!vis[v] && v != son[x]) Dfs2(v,v) ;
}
#define f(x) tr[x].f
#define w(x) tr[x].w
#define si(x) tr[x].si
#define val(x) tr[x].val
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define mid (tr[x].l+tr[x].r>>1)
inline void update(int x)
{
    w(x) = w(ls(x))+w(rs(x)) ;
    si(x) = max(si(ls(x)),si(rs(x))) ;
}
inline void pushup(int x)
{
    if(!f(x)) return ;
    f(ls(x)) += f(x),f(rs(x)) += f(x),si(ls(x)) += f(x),si(rs(x)) += f(x) ;
    w(ls(x)) += f(x)*val(ls(x)),w(rs(x)) += f(x)*val(rs(x)),f(x) = 0 ;
}
void Build(int x,int le,int ri)
{
    tr[x].l = le,tr[x].r = ri ; if(le == ri) {val(x) = eal[wh[le]] ; return ;}
    Build(ls(x),le,mid),Build(rs(x),mid+1,ri),val(x) = val(ls(x))+val(rs(x)) ;
}
void Modify(int x,int le,int ri,int k)
{
    if(tr[x].l >= le && tr[x].r <= ri)
    {
        si(x) += k,f(x) += k,w(x) += val(x)*k ;
        return ;
    } pushup(x) ;
    if(le <= mid) Modify(ls(x),le,ri,k) ; 
    if(mid < ri) Modify(rs(x),le,ri,k) ; update(x) ;
}
int Query(int x,int le,int ri)
{
    if(tr[x].l >= le && tr[x].r <= ri) return w(x) ; pushup(x) ;
    int res = 0 ; if(le <= mid) res = Query(ls(x),le,ri) ; if(mid < ri) res += Query(rs(x),le,ri) ; update(x) ;
    return res ; 
}
void TMY(int u,int k)
{
    while(pe[u] != 1) Modify(1,id[pe[u]],id[u],k),u = fa[pe[u]] ;
    Modify(1,1,id[u],k) ;
}
int TQY(int u)
{
    int res = sum1+sum2*dis[u] ;
    while(pe[u] != 1) res -= 2*Query(1,id[pe[u]],id[u]),u = fa[pe[u]] ;
    res -= 2*Query(1,1,id[u]) ; return res ;
}
int get_rt(int x)
{
    if(tr[x].l == tr[x].r) return wh[tr[x].l] ; pushup(x) ;
    return si(rs(x))*2 >= si(1)?get_rt(rs(x)):get_rt(ls(x)) ;
}
signed main()
{
//cerr<<(double)(&s_gnd-&S_GND)/1024.0/1024.0 ;
//	freopen(".in","r",stdin) ;
//	freopen(".out","w",stdout) ;
    read(n,q) ;
    FOR(i,2,n,1)
    {
        int u,v,w ; read(u,v,w) ;
        e[u].pb({v,w}),e[v].pb({u,w}) ;
    }
    Dfs1(1),me(vis,0),Dfs2(1,1),Build(1,1,n) ;
    while(q--)
    {
        int x,y ; read(x,y) ;
        sum1 += dis[x]*y,sum2 += y,TMY(x,y) ;
        print(TQY(get_rt(1))),enter ;     
        // print(get_rt(1)),enter ;
    }
    return 0 ;
}

标签:rt,sz,val,int,luogu,ZJOI2015,dis,P3345,define
From: https://www.cnblogs.com/snow-panther/p/17387100.html

相关文章

  • Luogu P5576 [CmdOI2019]口头禅 题解
    upd:修改了一些思路的表达,帮助理解。首先膜拜yyc大佬出这样的毒瘤好题。另外感谢永无岛、xtx1092515503、hs_black提供的思路。这里整理了一下这些思路,可能会有所启发。题意:给定一个字符串构成的序列,多次查询给定区间内各字符串的最长公共子串长度。提供一种后缀数组+......
  • Luogu P8890
    题面注意到直接根据题目的条件判断树是否美丽并不容易。考虑未被点亮的点,可以发现一棵树是美丽的当且仅当未被点亮的点形成一个连通块。有一个结论是,对于一个森林,点数减边数等于连通块的个数。\((*)\)因此树是美丽的当且仅当“未被点亮的节点的个数”减去“两端都未被点亮的边......
  • Luogu1772 [ZJOI2006] 物流运输
    传送门简化题意给你$m$个码头,码头之间有双向边连接,$n$天,其中一些码头在某些天会不可用,这$n$天都要有一条从$1$到$m$的路,每一次更换道路会需要$k$的代价,求这$n$天每天从$1$到$m$的距离之和与更改道路的价值之和的最小值。Solution首先我们能想到一个贪心的策......
  • [Luogu-P1007]题解(C++)
    PartIPreface原题目(Luogu)PartIISketch给定一个正整数\(L\),表示独木桥长度。给定一个正整数\(N\),表示桥上士兵的数量。给定\(N\)个整数,分别表示每个士兵的坐标。规定走到\(0\)坐标或\(L+1\)的位置为下桥,两个士兵相遇时不能走过去,他们会各自回头走。求出所有士......
  • 「ZJOI2015」地震后的幻想乡
    「ZJOI2015」地震后的幻想乡题意:给定一张图,每条边的边权在\([0,1]\)中随机,求最小生成树的最大边权的期望。其中这个很重要:对于\(n\)个\([0,1]\)之间的随机变量,第\(k\)小的那个的期望值是\(\frac{k}{n+1}\)那暴力就很容易了,假设我们已经按边权从小到大排好了序,也就是相......
  • Luogu P2973 [USACO10HOL]Driving Out the Piggies G
    发现答案其实与这个点炸弹经过的次数有关,因为只要知道了这个点炸弹经过次数\(w\),这个点答案就能算出:\(w\times\frac{p}{q}\)就想到设\(f_u\)为\(u\)点炸弹经过次数\(u\)点经过次数便可以由有连边的\(v\)点推来,要满足\(v\)点此时炸弹没爆炸且\(deg_v\)条边中选了\(......
  • Luogu P1298 最接近的分数 做题记录
    算是水紫,不过也学到一些有用的东西。题意给定正小数\(N\)。求分子不大于\(n\),分母不大于\(m\)的分数\(\dfrac{n}{m}\),使得\(\dfrac{n}{m}\)的值与\(N\)最接近(这里的最接近指的是\(|\dfrac{n}{m}-N|\)最小)。分析首先,大部分人都可以想到一个暴力:枚举\(i\in[1,......
  • Luogu P8007
    Upd.2022.2.3代码写的太烂,删了(题目传送门这题如果不仔细分析的话,很容易被当成DP白白浪费很多时间(就像我)。首先根据题意,可以认为左右括号是一种相互“抵消”的关系:对于每个左括号,它右面总要有且仅有一个对应的右括号与其配对,才能使其成为一个合法括号序列。在已知序列不无限......
  • Luogu P8496
    题面场外菜鸡whker听说你谷添加国赛新题立刻前来围观首先我们看到本题对于众数的定义,很容易想到通过权值线段树求解。(类似这题,但本题不需要可持久化)对于一个序列,我们维护一个deque和一个动态开点权值线段树。deque表示序列本身,线段树每个节点记录值在\([l,r]\)中的元素......
  • Luogu P3336
    因为我也是看了大佬的题解才写的(第一问),自认为自己讲得不可能比他们再好了,但是因为好多第二问的题解都被hack了,所以这里详细讲一下第二问的正确做法。初中平几课堂开课啦其实思路很简单,利用贪心的思想,能往上走就往上走,能走多高就走多高,来看这个图:点\(A\)是当前点,点\(B\)是......