由于本人太菜,所以字符串专题只学会了虚树一个知识点,所以写文乱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 ",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(' '); 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; } }