虚树学习小结

从一道模板题说起

  • 一道模板题:[SDOI2011]消耗战
  • 一个显然的东西,设f(x)表示x子树内的点能通过x走到根最小代价。如果x不是特殊点,f(x)=min(m[x], ∑ \sum f(son[x])),否则f(x)=m[x](其中m[x]表示x到根路径上的最短边),然而会T
  • 但是我们发现,有很多点是根本没有卵用的。同时,有用的转移,要么是他们本身,要么是几个不存在父子关系的结点的lca,当然m还是非常有卵用的。
  • 于是一个牛逼的东西诞生了——虚树
  • 虚树就是询问点以及他们两两之间的lca,最多有n-1个新产生的lca
  • 虚树的构建过程其实就是我们用一个栈维护一条链,将询问点按照dfs序从小到大加入,设k=lca(y,s[top])
  • 假如k=s[top],那么说明y是顺着这条链下去的,那么我们直接加入(但是在这题里面不用,因为下面的询问点就已经没用了)在这里插入图片描述
  • 否则,就说明当前栈里,以栈顶为端点的那条链已经完了,当前的y属于新的一条链,那就要将没用的那一部分删去直到dfn[s[top-1]]<dfn[k],然后仍有dfn[s[top]]>=dfn[k],那么我们要判断一下,s[top]是否是k,如果不是,要在虚树里,向s[top]连一条边,并将s[top]赋值为k,最后加入点y

在这里插入图片描述

  • 注意,将所有点加完之后,栈里面还有一条链,记得要弹出连边。
  • code(我感觉luogu的数据有些奇怪,c不是小于等于100000吗)
#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
#define Fo(i,x) for (int i=head[x];i;i=next[i])
using namespace std;
const int N=250000+255;
typedef long long ll;
int head[N],to[N*2],next[N*2],w[N*2],n,T,cnt,s[N],k,tot,x,y,z;
int size[N],fa[N],d[N],top[N],son[N],dfn[N],a[N];
ll m[N];
void add(int x,int y){
    to[++tot]=y;
    next[tot]=head[x];
    head[x]=tot;
}
void dfs(int x){
    size[x]=1;
    Fo(i,x){
        int v=to[i];
        if (v==fa[x]) continue;
        m[v]=min((ll)w[i],m[x]);fa[v]=x;d[v]=d[x]+1;
        dfs(v);
        size[x]+=size[v];
        if (size[son[x]]<size[v]) son[x]=v;
    }
}
void Dfs(int x,int T){
    dfn[x]=++cnt;
    top[x]=T;
    if (son[x]) Dfs(son[x],T);else return;
    Fo(i,x){
        int v=to[i];
        if (v==fa[x]||v==son[x]) continue;
        Dfs(v,v);
    }
}
int lca(int x,int y){
    while (top[x]!=top[y]){
        if (d[top[x]]<d[top[y]]) swap(x,y);
        x=fa[top[x]];
    }
    return d[x]<d[y]?x:y;
}
void ins(int x){
    if (k==1) { s[++k]=x; return; }//这里要加是因为,如果当前点与栈顶的lca是栈顶的话,我们是直接退出的(因为当前点无意义),又因为我们栈里面始终有一个点1压在里面,所以当k=1时,lca(x,s[top])肯定为1,如果没有这句话就加不到了。
    int l=lca(s[k],x);
    if (l==s[k]) return;
    while (k>1&&dfn[s[k-1]]>=dfn[l]) add(s[k-1],s[k]),k--;
    if (s[k]!=l) add(l,s[k]),s[k]=l;
    s[++k]=x;
}
ll dp(int x){
    if (!head[x]) return (ll)m[x];
    ll s=0;
    Fo(i,x){
        s+=dp(to[i]);
    }
    head[x]=0;
    return min(s,(ll)m[x]);
}
bool cmb(int a,int b){
    return dfn[a]<dfn[b];
}
int main(){
    //freopen("a.in","r",stdin);
    //freopen("a.out","w",stdout);
    scanf("%d",&n);
    fo(i,1,n-1) {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y);w[tot]=z;
        add(y,x);w[tot]=z;
    }
    m[1]=1ll<<60;
    dfs(1);
    Dfs(1,1);
    memset(head,0,sizeof(head));
    scanf("%d",&T);
    while (T--){
        scanf("%d",&n); 
        tot=0;
        fo(i,1,n) scanf("%d",&a[i]);
        sort(a+1,a+n+1,cmb);
        s[k=1]=1;
        fo(i,1,n) ins(a[i]);
        while (k) add(s[k-1],s[k]),k--;
        printf("%lld\n",dp(1));
    } 
    return 0;
}

  • [HNOI2014]世界树
  • 这类题,都有一个特点,就是询问的特殊点特别少,那么我们继续考虑用虚树来做。
  • 首先是中规中矩的套路,先把虚树搞出来,然后我们搞虚树上的每个点究竟是被那个点控制的,这个比较简单,dfs两遍就好,注意要先用儿子的更新父亲,再用父亲的更新儿子,不然会错,且将它记为con[x]。
  • 然后我们考虑虚树上每一条边(a,b)的答案,不算端点(不然会算重),假设x是距离a最近的点且是a的儿子,且在b到a的路径(原树)上。
  • 如果con[a]=con[b],那么原树上这条边对应的这些点都是由con[a]控制的,ans[con[a]]+=size[x]-size[b]
  • 否则,我们一定可以在中间找到一个分界点y(倍增实现),使得y以上由con[a]控制,y及以下由con[b]控制,ans[con[a]]+=size[x]-size[y],ans[con[b]=size[y]-size[b]
  • 注意到,原树上面会一些我们没有考虑过的子树,那么我们设rem[x]表示x为根的子树中还有多少个没有被计算,那么上面处理边的的时候,就要rem[a]-=size[x],初始的时候rem[x]=size[x],搞完边之后,rem[x]就加给,控制x的那个点即可。
  • 另外,为了整棵树都能算到,虚树的根的rem必须为整棵树的size,我们可以强制加入点1。
  • code
#include<cstdio>
#include<algorithm>
#include<cstring>
#define fo(i,a,b) for (int (i)=(a);(i)<=(b);(i)++)
#define fd(i,b,a) for (int (i)=(b);(i)>=(a);(i)--)
#define Fo(i,x) for(int i=head[x];i;i=next[i])
using namespace std;
const int N=300000+5;
int q[N],head[N],to[N*2],next[N*2],con[N],rem[N],ans[N],tot,g[N][21],d[N],size[N];
int n,m,dfn[N],cnt,s[N],top,x,y,a[N],b[N],l1,l2;
void R(int &x){
	int t=0,p=1;char ch;
	for (ch=getchar();!('0'<=ch&&ch<='9');ch=getchar())
		if (ch=='-') p=-1;
	for (;('0'<=ch&&ch<='9');ch=getchar())	
		t=t*10+ch-'0';
	x=t*p;
}
bool cmb(int x,int y){
	return dfn[x]<dfn[y];
}
void add1(int x,int y){
	//printf("%d %d\n",x,y);
	to[++tot]=y;
	next[tot]=head[x];
	head[x]=tot;
}
void add(int x,int y){
	to[++tot]=y;
	next[tot]=head[x];
	head[x]=tot;
//	printf("%d %d\n",x,y);
}
void dfs(int x,int y){
	size[x]=1;
	dfn[x]=++cnt;
	Fo(i,x){
		int v=to[i];
		if (v==y) continue;
		d[v]=d[x]+1;g[v][0]=x;
		dfs(v,x);
		size[x]+=size[v];
	}
}
int lca(int x,int y){
	if (d[x]<d[y]) swap(x,y);
	fd(k,20,0) if (d[g[x][k]]>=d[y]) x=g[x][k];
	if (x==y) return x;
	fd(k,20,0) if (g[x][k]!=g[y][k]) x=g[x][k],y=g[y][k];
	if (x^y) return g[x][0];
	return x;
}
int len(int x,int y){
	return d[x]+d[y]-2*d[lca(x,y)];
}
void ins(int x){
	int k=lca(x,s[top]);
	if (k==s[top]){ if (s[top]!=x) s[++top]=x; return; }//注意与上面的区别
	while (top>1&&dfn[s[top-1]]>=dfn[k]) 
		add1(s[top-1],s[top]),top--;
	if (s[top]^k) 
 		add1(k,s[top]),s[top]=k; 
	s[++top]=x;
}
void son(int x){
	rem[x]=size[x];
	q[++q[0]]=x;
	Fo(i,x){
		int v=to[i];
		son(v);
		if (!con[v]) continue;
		if (!con[x]) {con[x]=con[v];  continue;}
		l1=d[con[x]];l2=d[con[v]];
		if (l1>l2||(l1==l2)&&(con[x]>con[v])) con[x]=con[v];
	}
}
void fa(int x){
	Fo(i,x){
		int v=to[i];
		if (!con[v]) con[v]=con[x];
		l1=len(v,con[x]); l2=len(v,con[v]);
		if (l1<l2||(l1==l2)&&(con[x]<con[v])) con[v]=con[x];;
		fa(v);
	}
}
void sol(int a,int b){
	int x=b;
	fd(k,20,0) if (d[g[x][k]]>d[a]) x=g[x][k];
	rem[a]-=size[x];
	if (con[a]==con[b]){
		ans[con[a]]+=size[x]-size[b];
	}
	else{
		int y=b;
		fd(k,20,0){
			if (d[g[y][k]]<=d[a]) continue;
			l1=len(g[y][k],con[a]);l2=len(g[y][k],con[b]);
			if (l1>l2||(l1==l2)&&(con[a]>con[b])) y=g[y][k];
		}
		ans[con[a]]+=size[x]-size[y];
		ans[con[b]]+=size[y]-size[b];
	}
}
int main(){
	//freopen("a.in","r",stdin);
	//freopen("a.out","w",stdout);
	R(n);
	fo(i,1,n-1) R(x),R(y),add(x,y),add(y,x);
	g[1][0]=1;
	dfs(1,1);
	fo(j,1,20) fo(i,1,n) g[i][j]=g[g[i][j-1]][j-1];
	R(m);
	memset(head,0,sizeof(head));
	while (m--){
		tot=0;
		R(n);
		fo(i,1,n) R(a[i]),b[i]=a[i],con[a[i]]=a[i];
		sort(a+1,a+n+1,cmb);
		s[top=1]=1;
		fo(i,1,n) 	
			ins(a[i]);
		while (top>1) add1(s[top-1],s[top]),top--;
		q[0]=0;
		son(s[1]);fa(s[1]);
		fo(i,1,q[0]) {
			Fo(j,q[i]){
				sol(q[i],to[j]);
			}
		}
		fo(i,1,q[0]) ans[con[q[i]]]+=rem[q[i]];
		fo(i,1,n) printf("%d ",ans[b[i]]);
		printf("\n");
		fo(i,1,q[0]) ans[q[i]]=head[q[i]]=con[q[i]]=rem[q[i]]=0;
	}
	return 0;
}

后面的两道题,可能会等哪天复习虚树的时候在做。
[SDOI2015]寻宝游戏
[HEOI2014]大工程

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值