虚树

虚树

简介

如果题目中有这样子的字眼,给你 q 个询问,每个询问给一些点,这些点的总和不多,但是总的点数很多,意思就是绝大部分点用不到,这样子的情况,就考虑用虚树去优化。

一般的步骤

1.dfs预处理出LCA需要用到的数组,顺便搞出DFS序

// 经典的 两件套去搞出 LCA
void dfs1(int u,int pr) 
{
	de[u]=de[pr]+1;
	fa[u][0]=pr;
	id[u]=++idx;//顺便搞出dfs
	
	for(int i=1;i<26;i++)fa[u][i]=fa[fa[u][i-1]][i-1],mn[u][i]=min(mn[u][i-1],mn[fa[u][i-1]][i-1]);
	for(pi t2:v[u])
	{
		int j=t2.x,y=t2.y;
		if(j==pr)continue;
		mn[j][0]=y;
		dfs1(j,u);
	}	
}

int get(int x,int y)
{
	cc=INF;//这里是加了一步,要搞出这个路径上的最小值。
	if(de[x]<de[y])swap(x,y);
	for(int j=25;j>=0;j--)
	{
		if(de[fa[x][j]]>=de[y])
		{
			cc=min(cc,mn[x][j]);
			x=fa[x][j];
		}
	}	
	if(x==y)return x;
	for(int j=25;j>=0;j--)
	{
		if(fa[x][j]!=fa[y][j])
		{
			cc=min(cc,min(mn[x][j],mn[y][j]));
			x=fa[x][j];
			y=fa[y][j];
		}
	}
	return fa[x][0];
}

2.build

先将这一个询问中的点按照dfs序列排序,方便后面操作。然后用一个栈来模拟这个建新边的过程,每次都求一下LCA(当前点和栈顶元素)。

然后对于这个Lca 有三种情况,这里面每一次的get 是为了找出这条链上的最小值,从而方便建边

  1. 和栈顶相同,2. dfs序比栈中第二个的dfs序小, 3.dfs序比栈中第二个的dfs序大。
1. 和栈顶相同
    // g是新边 h数组是本次询问的点 stk是栈, 
    // 情况1 的话说明这个点是第一次被放入,所以捏,要清空一下数组,同时入栈。
    g[h[i]].clear();stk[++top]=h[i];
	// 这个操作每一次都会进入。表示这个点在栈中。

2.dfs序比栈中第二个的dfs序小
     //这里需要用到while 因为每次要知道遇到情况1为止。
	//对于这个栈的话,我们在栈的第二个点连一条边到栈顶。
    while(id[f]<id[stk[top-1]])
    {
        int u=stk[top-1],v=stk[top];get(u,v);
        g[u].ps({v,cc}),top--;
    }
3.dfs序比栈中第二个的dfs序大。
    // 这一段的意思就是,如果我的f 比栈中第二个的dfs 大,
    //说明这个Lca没有进去过,要考虑怎么把这个Lca放进去,也就是 连一条从f到 栈顶的边
    if(id[f]>id[stk[top-1]])
    {
        g[f].clear();
        int u=f,v=stk[top];get(u,v);
        g[u].ps({v,cc}),top--;
        stk[++top]=f;
    }


4.最后清空一下stk
    while(top>1)
	{
		int u=stk[top-1],v=stk[top];
		get(u,v);
		g[u].ps({v,cc}),top--;
	}
    return;

    

最后愉快的在这个新树上Dp

消耗战

题意:

给你 q 个询问,每个询问一些点,然后问这些点中,怎么删边能用最小的边权和 让所有给定的点和 1 号点不联通。

思路:

都把这个作为虚树的板子题了,还能怎么写 (O.o)!! 先考虑暴力,如果一个点的儿子一定要删除,那么这个点的权值+w,如果不一定删除,那么就考虑min (删除当前这条边,子树中的一个个删除)。

void dfs(int u,int pr)
{
	for(pi t2:v[u])
	{
		int j=t2.x,w=t2.y;
		if(j==pr)continue;
		dfs(j,u);
		if(is[j])d[u]+=w;
		else d[u]+=min(d[j],w);	
}

正解也就是在这个模型上 加上虚树去减少这个点的数量,同时用Lca 去快速找出一条链上的最小值。

Code

const int N=3e5+100,mod=1e9+7,INF=1e10;
int lowbit(int x){return x&-x;}
int gcd(int a,int b){return a%b==0?b:gcd(b,a%b);}

int d[N];
vector<pi>v[N];
vector<pi>g[N];
int is[N],id[N],idx,fa[N][26],de[N];
set<int>s;
int w[N],h[N],k,stk[N],dfn[N];
int cc,mn[N][26];

void add(int a,int b,int c)
{
	v[a].ps({b,c});
}
bool cmp(int x,int y) 
{
	 return id[x]<id[y]; 
}
void build() 
{
	cin>>k;
	s.clear();
	for(int i=1;i<=k;i++)
	{
		cin>>h[i];
		s.insert(h[i]);
	}
	sort(h+1,h+1+k,cmp);
	int top=0;
	stk[++top]=1;g[1].clear();
	for(int i=1;i<=k;i++)
	{
	//	if(h[i]==1)continue;
		// cout<<h[i]<<endl;
		int f=get(h[i],stk[top]);
		
		if(f!=stk[top])
		{
			while(id[f]<id[stk[top-1]])
			{
				int u=stk[top-1],v=stk[top];get(u,v);
				g[u].ps({v,cc}),top--;
			}
			if(id[f]>id[stk[top-1]])
			{
				g[f].clear();
				int u=f,v=stk[top];get(u,v);
				g[u].ps({v,cc}),top--;
				stk[++top]=f;
			}
			else 
			{
				int u=stk[top-1],v=stk[top];
				get(u,v);
				g[u].ps({v,cc}),top--;
			}
		}
		g[h[i]].clear();stk[++top]=h[i];
		
	}
	while(top>1)
	{
		int u=stk[top-1],v=stk[top];
		get(u,v);
		g[u].ps({v,cc}),top--;
	}
    return;
}

void dfs2(int u,int pr)
{
	d[u]=0;
	// cout<<u<<endl;
	for(pi t2:g[u])
	{
		int j=t2.x,y=t2.y;
		if(j==pr)continue;
		dfs2(j,u);
		if(s.count(j))d[u]+=y;
		else d[u]+=min(y,d[j]);
	}
}

void solve()
{
	int n;cin>>n;
	memset(mn,0x3f,sizeof mn);
	for(int i=1;i<n;i++)
	{
		int a,b,c;cin>>a>>b>>c;
		add(a,b,c);add(b,a,c);
	}
	dfs1(1,1);
	int q;cin>>q;
	while(q--)
	{
		build();
		dfs2(1,-1);
		cout<<d[1]<<endl;
	}
}

signed main()
{
	kd;
	int _;_=1;
	//cin>>_;
	while(_--)solve();	
	return 0;
}
posted @ 2022-08-12 22:40  黄小轩  阅读(31)  评论(0)    收藏  举报