51nod 1576 Tree and permutation(树的重心+dfn序)

乍一看我不会。
先不考虑加点。
先考虑没有那个除\(2\)
考虑每一条边的贡献,假设某一条边把这棵树分成大小为x,y的两个部分。
那么这条边最多可以被使用\(min(x,y)*2\)次(因为有进有出),即贡献最大为\(min(x,y)*2*\)这条边的权值。
那么能不能让每一条边的被使用达到最大呢?
显然可以。
那怎么快速算这个东西呢?不可能每加一个点就dfs一遍吧。那样就\(T\)飞了。
实际上这个东西就是每个点到树的重心的距离\(*2\)
为什么?因为满足以树的重心为根每一个子树大小\(<\)总共的节点数。每一棵子树内所有点都要向子树外也就是根(重心)连边。
然后发现这个除\(2\)没有向下取整。
所以就是求所有的点到重心的距离和。
然后加点的话可以离线然后\(dfn序\)维护一下。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
#define ls now<<1
#define rs now<<1|1
const int N=101000;
int cnt,head[N];
struct edge{
    int to,nxt;
}e[N*2];
void add_edge(int u,int v){
    cnt++;
    e[cnt].nxt=head[u];
    e[cnt].to=v;
    head[u]=cnt;
}
int size[N],fa[N][22],dep[N],dfn[N],tot;
void dfs(int u,int f){
    size[u]=1;
    dfn[u]=++tot;
    fa[u][0]=f;dep[u]=dep[f]+1;
    for(int i=1;i<=20;i++)fa[u][i]=fa[fa[u][i-1]][i-1];
    int maxson=-1;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==f)continue;
        dfs(v,u);
        size[u]+=size[v];
    }
}
int getlca(int x,int y){
    if(dep[x]<dep[y])swap(x,y);
    for(int i=20;i>=0;i--)
        if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
    if(x==y)return x;
    for(int i=20;i>=0;i--)
        if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
int sum[N*4];
void update(int now){
    sum[now]=sum[ls]+sum[rs];
}
void build(int l,int r,int now){
    if(l==r){
        if(l==1)sum[l]=1;
        return ;
    }
    int mid=(l+r)>>1;
    build(l,mid,ls);
    build(mid+1,r,rs);
    update(now);
}
void add(int l,int r,int x,int now){
    if(l==r){
        sum[now]=1;
        return;
    }
    int mid=(l+r)>>1;
    if(x>mid)add(mid+1,r,x,rs);
    else add(l,mid,x,ls);
    update(now);
}
int check(int l,int r,int L,int R,int now){
    if(l==L&&r==R)return sum[now];
    int mid=(l+r)>>1;
    if(L>mid)return check(mid+1,r,L,R,rs);
    else if(R<=mid)return check(l,mid,L,R,ls);
    else return check(l,mid,L,mid,ls)+check(mid+1,r,mid+1,R,rs);
}
int up(int x,int y){
    for(int i=20;i>=0;i--)
        if(dep[fa[x][i]]>dep[y])x=fa[x][i];
    return x;
}
int read(){
    int sum=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
    return sum*f;
}
int n,root;
long long ans;
int main(){
    n=read();n++;
    root=1;ans=0;
    for(int i=2;i<=n;i++){
        int v=read();
        add_edge(i,v);add_edge(v,i);
    }
    dfs(1,0);
    build(1,n,1);
    for(int i=2;i<=n;i++){
        add(1,n,dfn[i],1);
        int lca=getlca(root,i);
        ans+=(long long)(dep[root]+dep[i]-2ll*dep[lca]);
        if(lca==root){
            int x=up(i,root);
            int sizex=check(1,n,dfn[x],dfn[x]+size[x]-1,1);
            if(sizex>i-sizex)root=x,ans+=(long long)(i-sizex*2ll);
        }
        else{
            int x=fa[root][0];
            int sizex=check(1,n,dfn[root],dfn[root]+size[root]-1,1);
            if(i-sizex>sizex)root=x,ans+=(long long)(sizex*2ll-i);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

转载于:https://www.cnblogs.com/Xu-daxia/p/10479699.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值