虚树入门

今天学了一下虚树,简单谈一下有什么用。

    所谓虚树,其实就是把询问中需要用到的点建到另一棵树上,比如如果我们当前询问一条链上的两个端点,原本如果做dfs dp的话我们的复杂度是o(n)的,但是如果是虚树,那么树上就只会有这两个端点,两个端点之间的那条边记录了原本整条链上的信息。于是复杂度变成了o(2)的。

    那么怎么建虚树呢?一般题中除了给出的询问点(之后成为关键点),我们还会用到关键点的lca(我不知道一般是lca还是一定是lca。),但是lca数也是o(n^2)的,于是我们想到把关键点按dfs序排序,比如设有三个点x,y,z,,他们按dfs序排序,lca(y,z)一定在lca(x,y)或者lca(y,z)中(其实本来想想也知道,一棵树上k个点的lca不管怎么组合应该是不超过k-1个的)。于是我们现在找到了2*k级别的点,问题是怎么把他们丢到一棵树上去。

    我们先再次把所有点按dfs序排序,然后我们考虑这样一种方法,我们维护一个栈,如果栈为空,直接加点,否则我们看栈顶的点是不是当前点的祖先,如果不是,那么弹出栈顶继续,否则就可以把当前点加进栈了(然后从栈顶向这个点连一条边)。(因为之前插入的点可能是和当前点是并列关系,这个不一定会需要用到lca,因为我们记录一个dfs时入队和出队的顺序就可以了)。(第一个点就直接作为根了)。

#include <iostream>
#include <cstring>
#include <cstdlib>
#include <string>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <ctime>
#define inf (long long)100000000
using namespace std;
struct node {int to;int next;int len;
};node edge[500010],bian[2000010];
int in[200010],out[200010],dep[200010],f[200010][21],root;
int tim = 0,fir[200010],first[200010],n,a,b,m,Q,stack[200010];
int sum=0,size=0,len,top,list[200010],que[200010];
long long Dp[200010][3];
bool instack[200010],mark[200010];
bool comp(const int &x,const int &y) {return in[x] < in[y];}
bool check(int x,int y) {return in[x] <= in[y] && out[x] >= out[y];}
void add(int x,int y) {
	edge[ ++ sum].to = y;
	edge[sum].next = fir[x];
	fir[x] = sum;
}
void inser(int x,int y,int z) {
	bian[ ++ size].to = y;
	bian[size].next = first[x];
	first[x] = size;
	bian[size].len = z;
}
void dfs(int x,int depth,int Anc) {
	dep[x] = depth;
	f[x][0] = Anc;
	in[x] = ++ tim;
	for(int i = 1;i <= 20;i ++) 
	    f[x][i] = f[f[x][i - 1]][i - 1];
	for(int u = fir[x];u;u = edge[u].next) 
	    if(dep[edge[u].to] == 0)
	        dfs(edge[u].to,depth + 1,x);
	out[x] = ++ tim;
} 
int lca(int x,int y) {
	if(dep[x] < dep[y]) swap(x,y);
	for(int i = 20;i >= 0;i --) 
		if(dep[f[x][i]] >= dep[y]) 
			x = f[x][i];
	if(x == y) return x;
	for(int i = 20;i >= 0;i --) 
	    if(f[x][i] != f[y][i])
	        x = f[x][i],y = f[y][i];
	return f[x][0];
}
int main() {
	scanf("%d",&n);
	for(int i = 1;i <= n - 1;i ++) {
		scanf("%d%d",&a,&b);
		add(a,b);
		add(b,a);
	}
	dfs(1,1,1);
	scanf("%d",&Q);
	while(Q --) {
		scanf("%d",&m);len = m;top = 0;root = 0;
		for(int i = 1;i <= m;i ++) scanf("%d",&list[i]),que[i] = list[i];
		sort(list + 1,list + m + 1,comp);
		for(int i = 1;i < m;i ++) 
		    list[++ len] = lca(list[i],list[i + 1]);
		sort(list + 1,list + len + 1,comp);
		len = unique(list + 1,list + len + 1) - list - 1;
		for(int i = 1;i <= len;i ++) {
			while(top > 0 && !check(stack[top],list[i]))
			    top --;
			if(stack[top] == 0) root = list[i];
			else {
				int s = stack[top],t = list[i];
				inser(s,t,dep[t] - dep[s]);
			}
			stack[ ++ top] = list[i];
		}
		for(int i = 1;i <= len;i ++) instack[list[i]] = true;
		for(int i = 1;i <= m;i ++) mark[que[i]] = true;
		//dp(root);
		for(int i = 1;i <= len;i ++) instack[list[i]] = false;
		for(int i = 1;i <= m;i ++) mark[que[i]] = false;
		for(int i = 1;i <= len;i ++) first[list[i]] = 0;size = 0;
	}
	return 0;
}




  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值