虚树套餐(随缘更新)

旧城冷巷雨未停 提交于 2020-08-19 19:06:52

由于本人太菜,所以字符串专题只学会了虚树一个知识点,所以写文乱giao

首先假装大佬地讲一下虚树:

虚树主要用于优化树DP,由于有些题hin贱,会有多次查询修改,还只查询一些关键点,此时直接树DP就跑不过去了。

这时我们想到,能不能减少点的个数,使树DP能跑得飞快呢?

学的时候联想到了这题,说不定也能用虚树做

我们通过把一条链压缩成一条边来简化这棵树

比如这棵树,黑色点是关键点,简化后就变成了

这时我们懒得遍历2 3 6 4这些节点了,反正也没用(-_-)

但是7号节点还是有必要留,毕竟还要递归嘛~~

但怎么找到所有要保留的点并连边呢

维护右链

我们开一个栈,记录当前“最靠右的链”

先连接1号节点和最靠左的关键节点 8号节点

 

(红色是关键点,蓝色是右链,节点3 7都被压成了一条边) 

(是的,右链一开始是在左边的)

然后我们要让右链不断“往右移”

我们于是把更加“靠右”(是的,就是 物 理 靠 右 )的9号点添加进来,但是我们不能“把一个点加到一条边上”啊

我萌就把右链底端的8号点和新加入的9号点的lca:7号点还原,从而加入9

重复以上操作就可以构建出虚树

什么,你问怎么找到“物理靠右”?肯定是dfs序啦.

void build_vtree(ll x){
    if(tt==1){
        st[++tt]=x;
        return ;
    }
    ll lca=L_C_A(st[tt],x);
    while(tt>1&&dfn[st[tt-1]]>=dfn[lca]){
        add2(st[tt-1],st[tt]);
        tt--;
    }
    if(lca!=st[tt]){
        add2(lca,st[tt]);
        st[tt]=lca;
    }
    st[++tt]=x;
}

int main(){
    .............
    while(tt>1){
        add2(st[tt-1],st[tt]);
        tt--;
    } 
    .............
}

st是我们用来记录右链的栈(tt是top),需要注意的是,我们不是直接把st里的点都连接上

而是在退栈时才连接,这样就能把lca们都连上了

例题

消耗战

题意:ZEZ是一位高级玩家,他在一款战略游戏中把游戏蒟蒻FJH逼入了墙角,现在ZEZ胜利在望,FJH只剩下一座碉堡,ZEZ需要切断一些碉堡与外界的通道,让DFH无法获得食物,就能击败他

但是FJH是一位玄学高手,他的粮食生产基地的位置会随着时间变化而变化,所以你需要每次都切断他的粮食来源,才能保证ZEZ的胜利(详细请看洛谷题面)

先考虑下暴力树DP

我们只要在经过每个节点x的时候,考虑切掉x子树中一些的边,来切断关键点与根节点的联系(孤立掉关键点)

然后虚树优化看代码就好  

需要注意直接memset必超时

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define N 250010
using namespace std;
ll hh[N],head[N],dfn[N],f[30][N],dep[N],st[N],dis[N],mn[N];
ll n,m,cnt,ret,top,tt;

struct The_World{
    ll to,nxt,w;
}q[N*10];

struct Star_Platinum{
    ll v,nx;
}e[N*10];

struct key_point{
    ll num,df;
    bool operator <(const key_point&tmp )const{
        return df<tmp.df;
    }
}a[N];

void add(ll u,ll v,ll w){
    q[++cnt].to=v;
    q[cnt].nxt=head[u];
    q[cnt].w=w;
    head[u]=cnt;
}

void add2(ll u,ll v){
    e[++ret].v=v;
    e[ret].nx=hh[u];
    hh[u]=ret;
}

void dfs(ll x,ll fa){
    dep[x]=dep[fa]+1;
    dfn[x]=++top;
    for(int i=1;i<=19&&f[i-1][x];i++){
        f[i][x]=f[i-1][f[i-1][x]];
    }
    for(ll i=head[x];i;i=q[i].nxt){
        ll v=q[i].to;
        if(v!=fa){
            ll v=q[i].to;
            if(!dfn[v]){
                f[0][v]=x;
                mn[v]=min(mn[x],q[i].w);
                dfs(v,x);
            }
        }
    }
}

ll LYCA(ll x,ll y){
    if(dep[y]>dep[x])swap(x,y);
    for(int i=20;i>=0;i--){
        if(dep[f[i][x]]>=dep[y]){
            x=f[i][x];
        }
        if(x==y)return x;
    }
    for(int i=20;i>=0;i--){
        if(f[i][x]!=f[i][y]){
            x=f[i][x];
            y=f[i][y];
        }
    }
    return f[0][x];
}

void make_vtree(ll x){
    if(tt==1){
        st[++tt]=x;return ;
    }
    ll lca=LYCA(st[tt],x);
    if(lca==st[tt])return ;
    while(tt>1&&dfn[st[tt-1]]>=dfn[lca]){
        add2(st[tt-1],st[tt]);
        tt--;
    }
    if(lca!=st[tt]){
        add2(lca,st[tt]);
        st[tt]=lca;
    }
    st[++tt]=x;
}

ll DP(ll x,ll fa){
    ll sum=0;
    bool poo=0;
    for(ll i=hh[x];i;i=e[i].nx){
        ll v=e[i].v;
        if(v!=fa){
            sum+=DP(v,x);
            poo=1;
        }
    }
    hh[x]=0;
    if(sum)return min(sum,mn[x]);
    return mn[x];
}

int main(){
    mn[1]=1ll<<60;
    scanf("%lld",&n);
    for(int i=1;i<n;i++){
        ll a1,a2,a3;
        scanf("%lld%lld%lld",&a1,&a2,&a3);
        add(a1,a2,a3);
        add(a2,a1,a3);
    }
    dfs(1,0);
    scanf("%lld",&m);
    for(int i=1;i<=m;i++){
        ll a1;
        tt=0,ret=0;
        scanf("%lld",&a1);
        for(ll i=1;i<=a1;i++){
            scanf("%lld",&a[i].num);
            a[i].df=dfn[a[i].num];
        }
        sort(1+a,1+a+a1);
        st[++tt]=1;
        for(ll i=1;i<=a1;i++){
            if(a[i].num==1)continue;
            make_vtree(a[i].num);
        }
        while(tt>0){
            add2(st[tt-1],st[tt]);
            tt--;
        }
        printf("%lld\n",DP(1,0));
    }
}

 

一道很烦的题

世界树

挺烦的,居然有人用了6遍DFS

先构建虚树

定义belx为x归属的点(就是管理x节点的那个议事处),disx为x到belx的距离

然后我们用两次dfs,第一次向上寻找belx,第二次dfs在dp前先向下找belx

那么怎么处理每个议事处管理的点呢,定义six为x控制的节点数量,si初始化为sizx

那么定义bau(u为议事处)为x控制的种族个数,那么bau=Σsix(x为被u控制的关键点(不要忘记我们建了虚树哦))

当belx==belv的时候six直接减siv是吧

但是当不等于是,就说明x->v这条链中,有些点属于belx,有些属于belv

那么我们就倍增找出这个分割点,然后分别统计到six和siv

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define vv q[i].to
#define v1 e[i].v
#define N 400000
using namespace std;
ll head[N],hh[N],dfn[N],f[30][N],st[N],dep[N],dis[N],bel[N],siz[N],ba[N],si[N],tr[N];
ll n,m,qu,cnt,ret,top,tt,tpp;
bool pool[N];
/////////需要注意v1是虚树的v vv是原树的v 

struct key_point{
    ll num,ii,df;
}a[N];

struct The_World{
    ll to,nxt;
}q[N+10];

struct Star_Platinum{
    ll nx,v,w;
}e[N+10];

bool cmp1(key_point a,key_point b){return a.df<b.df;}

bool cmp2(key_point a,key_point b){return a.ii<b.ii;}

void add1(ll u,ll v){
    q[++cnt].to=v,q[cnt].nxt=head[u],head[u]=cnt;
    q[++cnt].to=u,q[cnt].nxt=head[v],head[v]=cnt;
}

void add2(ll u,ll v,ll w){
    e[++ret].v=v,e[ret].nx=hh[u],e[ret].w=w,hh[u]=ret;
    e[++ret].v=u,e[ret].nx=hh[v],e[ret].w=w,hh[v]=ret;
}

void dfs(ll x,ll fa){
    dfn[x]=++top;
    dep[x]=dep[fa]+1;
    siz[x]=1;
    for(ll i=1;i<=20&&f[i-1][x];i++){
        f[i][x]=f[i-1][f[i-1][x]];
    }
    for(ll i=head[x];i;i=q[i].nxt){
        if(vv!=fa){
            if(!dfn[vv]){
                f[0][vv]=x;
                dfs(vv,x);
                siz[x]+=siz[vv];
            }
        }
    }
}

ll L_Y_C_A(ll x,ll y){
    if(dep[y]>dep[x])swap(x,y);
    for(ll i=20;i>=0;i--){
        if(dep[f[i][x]]>=dep[y]){
            x=f[i][x];
        }
        if(x==y)return x;
    } 
    for(ll i=20;i>=0;i--){
        if(f[i][x]!=f[i][y]){
            x=f[i][x];
            y=f[i][y];
        }
    }
    return f[0][x];
}

void build_vtree(ll x){
    if(tt==1){
        st[++tt]=x;
        return ;
    }
    ll lca=L_Y_C_A(st[tt],x);
    while(tt>1&&dfn[st[tt-1]]>=dfn[lca]){
        add2(st[tt-1],st[tt],dep[st[tt]]-dep[st[tt-1]]);
        tt--;
    }
    if(lca!=st[tt]){
        add2(lca,st[tt],dep[st[tt]]-dep[lca]);
        st[tt]=lca;
    }
    st[++tt]=x;
}

ll CA(ll x,ll kk){
    if(dep[x]<kk)return x;
    for(ll i=20;i>=0;i--){
        if(dep[f[i][x]]>=kk)x=f[i][x];
    }
    return x;
}

void DP1(ll x,ll fa){
    tr[++tpp]=x;
    si[x]=siz[x];
    if(pool[x]){
        dis[x]=0,bel[x]=x;
    }
    else dis[x]=1e9;
    for(ll i=hh[x];~i;i=e[i].nx){
        if(v1!=fa){
            DP1(v1,x);
            if(dis[x]>dis[v1]+e[i].w||(dis[x]==dis[v1]+e[i].w&&bel[x]>bel[v1])){
                bel[x]=bel[v1];
                dis[x]=dis[v1]+e[i].w;
            }
        }
    }
}

void DP2(ll x,ll fa){
    for(ll i=hh[x];~i;i=e[i].nx){
        if(fa!=v1){
            if(dis[v1]>dis[x]+e[i].w||(dis[x]+e[i].w==dis[v1]&&bel[v1]>bel[x])){
                dis[v1]=dis[x]+e[i].w;
                bel[v1]=bel[x];
            }
            DP2(v1,x);
            if(bel[x]==bel[v1]){
                si[x]-=siz[v1];
            }
            else {
                ll d=dis[v1]+dis[x]+dep[v1]-dep[x]-1,k;//向下取整
                k=d/2-dis[v1];
                ll tmp=CA(v1,dep[v1]-k);
                if((d&1)&&bel[x]>bel[v1]&&k>=0)tmp=f[0][tmp];
                si[v1]+=siz[tmp]-siz[v1];
                si[x]-=siz[tmp]; 
            }
            ba[bel[v1]]+=si[v1];
        }
    }
    if(x==1)ba[bel[x]]+=si[x];
}

int main(){
    scanf("%lld",&n);
    for(ll i=1;i<n;i++){
        ll a1,a2;
        scanf("%lld%lld",&a1,&a2);
        add1(a1,a2);
    }
    dfs(1,0);
    memset(hh,-1,sizeof hh);
    scanf("%lld",&m);
    while(m--){
        ret=0;
        ll a1;
        scanf("%lld",&a1);
        for(ll i=1;i<=a1;i++){
            scanf("%lld",&a[i].num);
            a[i].ii=i,a[i].df=dfn[a[i].num];
            pool[a[i].num]=1;
        }
        sort(1+a,1+a+a1,cmp1);
        tt=1;st[tt]=1;
        for(ll i=1;i<=a1;i++){
            if(a[i].num==1)continue;
            build_vtree(a[i].num);
        }
        while(tt>1){
            add2(st[tt-1],st[tt],dep[st[tt]]-dep[st[tt-1]]);
            tt--;
        }
        DP1(1,0);
        DP2(1,0);
        sort(1+a,1+a+a1,cmp2);
        for(ll i=1;i<=a1;i++){
            printf("%lld ",ba[a[i].num]);
        }
        putchar('\n');
        for(int i=1;i<=tpp;i++){
            pool[tr[i]]=0;
            hh[tr[i]]=-1;
            si[tr[i]]=dis[tr[i]]=bel[tr[i]]=ba[tr[i]]=0;
        }
        tpp=0;
    }
}

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!