ZJOI幻想乡战略游戏【题解】

7 篇文章 0 订阅
6 篇文章 0 订阅

前言

动点分思维难度还是高些,不像点分那样板(这也是为为什么写题解的原因)。但其实也不难,像我这种动点分学都没学只知道要建点分树的蒟蒻都可以 大力 yy一波(为了研究各种性质捣鼓一个晚上)

题面

题面就不粘了,搞个链接

sol

分治树就是一个数据结构。大部分优秀的数据结构都是带log的,这是因为利用了化小子问题或是利用线性来作为原理支撑。
数据结构的优秀是建立在修改与查询几乎相等或者不知道两者分布的情况下的。因为当一者远小于另一者时,可以采用一者使用O(1),而另一种采用O(n)来运算。
动态点分治一定要想象做一次单一的分治如何处理。
和在一个序列上分治一样,点分树实际上是记录了每一层分治的状态。分治时,我们是要考虑一半对另一半的贡献。点分树也是一样,先考虑相邻子树的贡献,在考虑与更高级子树的贡献,在考虑更更高级的子树的贡献。所以这样导致修改也要按这个顺序。
这样做的好处是能保证自己的修改能影响到每一个节点,每一个节点的修改也能影响到自己。
树上也具有二分的性质。也就是说,带权重心与重心相似的性质——对一个节点来说,如果他的儿子作为关键点比他要优,那么重心一定是往他的儿子方向移动。这个性质使其具有二分的能力。
我们可以借助点分树来实现这个log;

代码

#include<bits/stdc++.h>
#define LL long long
using namespace std;
inline char gc(){
    static char buf[1<<6],*p1=buf,*p2=buf;
    return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,1<<6,stdin),p1==p2)?EOF:*p1++;
}
template <class T>
inline void read(T&data){
    data=0;
    register char ch=0;register int caa=1;
    while((ch<'0'||ch>'9')&&ch!='-')ch=gc();
    ch=='-'?caa=-1,ch=gc():caa=1;
    while(ch<='9'&&ch>='0'){
        data=(data<<3)+(data<<1)+(ch&15);
        ch=gc();
    }
    data*=caa;
}
const int _ (1e5+1e2),INF (2e9);
struct edge1{
    int nt,to,len;
}e1[_<<1];
int head1[_],lg[_<<1],cnt,dist,n,head2[_],m,st[_<<1][22],dfn[_],size[_],MX,all,root,vis[_],par[_],fdfn[_],app[_],dd;
struct edge2{
    int nt,to1,to2;
}e2[_<<1];
LL tofa[_],own[_],fake_ans,sum[_],ans,dis[_];
inline void add1(register int a,register int b,register int c){e1[++cnt].to=a,e1[cnt].nt=head1[b],e1[cnt].len=c,head1[b]=cnt;}
inline void add2(register int a,register int b,register int c){e2[++cnt].to1=a,e2[cnt].to2=c,e2[cnt].nt=head2[b],head2[b]=cnt;}
void getroot(register int now,register int fa){
    size[now]=1;int mx=0;
    for(register int i=head1[now];i;i=e1[i].nt){
        if(vis[e1[i].to]||e1[i].to==fa)continue;
        getroot(e1[i].to,now),size[now]+=size[e1[i].to],mx=max(mx,size[e1[i].to]);
    }
    mx=max(mx,all-size[now]);
    if(mx<MX){root=now,MX=mx;}
    return;
}
void dfsI(register int now,register int fa){
    dfn[now]=++cnt,fdfn[cnt]=now,st[++dd][0]=cnt,app[now]=dd;
    for(register int i=head1[now];i;i=e1[i].nt){
        if(e1[i].to==fa)continue;
        dis[e1[i].to]=dis[now]+e1[i].len;
        dfsI(e1[i].to,now);
        st[++dd][0]=dfn[now];
    }
}
void divide(register int now){
    vis[now]=1;
    for(register int i=head1[now];i;i=e1[i].nt){
        if(vis[e1[i].to])continue;
        all=size[e1[i].to],MX=INF,
            getroot(e1[i].to,now),
            par[root]=now,
            add2(root,now,e1[i].to),
        divide(root);
    }
}
inline void init(){
    register int us=2*n-1;
    for(register int i=2;i<=us;++i)lg[i]=lg[i>>1]+1;
    for(register int j=1;j<=17;++j)
        for(register int i=1;i<=us&&( i+(1<<(j-1)) )<=us;++i)
            st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
inline LL getdis(register int uu,register int vv){
    register int u=app[uu],v=app[vv];if(u>v)swap(u,v);
    register int lca=fdfn[ min(st[u][lg[v-u+1]],st[v+1-(1<<lg[v-u+1])][lg[v-u+1]]) ];
    return dis[uu]+dis[vv]-2LL*dis[lca];
}
inline void modify(register int now,register int zh){
    sum[now]+=zh;
    for(register int i=par[now],j=now;i;j=i,i=par[i]){
        dist=getdis(now,i);
        sum[i]+=zh,own[i]+=1LL*zh*dist,tofa[j]+=1LL*zh*dist;
    }
}
inline LL solve(register int now){
    register LL ret=own[now];
    for(register int i=par[now],j=now;i;j=i,i=par[i]){
        ret=ret+own[i]-tofa[j],
        ret=ret+(sum[i]-sum[j])*getdis(i,now);
    }
    return ret;
}
void query(register int now){
    ans=solve(now);
    for(register int i=head2[now];i;i=e2[i].nt){
        fake_ans=solve(e2[i].to2);
        if(fake_ans<ans){query(e2[i].to1);return;}
    }
    return;
}
int main(){
    //freopen("data.in","r",stdin);
    //freopen("1.out","w",stdout);
    read(n),read(m);
    //cout<<n<<' '<<m;exit(0);
    for(register int i=1,a,b,c;i<n;++i)read(a),read(b),read(c),add1(a,b,c),add1(b,a,c);
    cnt=0,dfsI(1,0),MX=INF,all=n,cnt=0;
    getroot(1,0);
    register int rrtt=root;
    cnt=0,divide(root),init();
    for(register int i=1,x,y;i<=m;++i){
        read(x),read(y);
        modify(x,y),ans=0,query(rrtt);
        printf("%lld\n",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值