由于本人太菜,所以字符串专题只学会了虚树一个知识点,所以写文乱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;
}
}
来源:oschina
链接:https://my.oschina.net/u/4304562/blog/4449180