[SDOI2011]消耗战(虚树)

124 篇文章 0 订阅
105 篇文章 0 订阅

虚树就是由树中的几个点及他们的LCA构成的简化的树,因为树的点数被减小,复杂度也随着降低。
首先易证n个点的不同LCA最多只有n-1个,虚树的复杂度就有了保障。
然后我们考虑按照dfs序一个一个点的加入,并用栈维护当前虚树的右链(这里的右链指的是最右端的链,不一定是右儿子),那么新加入的点x和之前虚树的LCA就是右链最下端的点与x的lca。
然后再维护右链就行。
虚树难在清零和处理非虚树节点的贡献。。。。。。
大佬博客
AC Code:

#include<bits/stdc++.h>
#define maxn 250005
#define LL long long
#define lim 18
using namespace std;

int n;
int info[maxn],Prev[maxn<<1],to[maxn<<1],cnt_e;
int del[maxn],cdel;
inline void Node(int u,int v)
{
	if(!info[u]) del[cdel++] = u;
	Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v;
}
vector<int>G[maxn],W[maxn];
int esiz[maxn];

int Min[lim][maxn],f[lim][maxn],dep[maxn],idx[maxn],tot;
int c[maxn],Q[maxn],tp;
bool is[maxn];

void dfs(int now,int ff)
{
    dep[now] = dep[f[0][now] = ff] + 1;
    idx[now] = ++ tot;
	for(int i=0;i<esiz[now];i++)
		if(G[now][i]!=ff)
		{
			dfs(G[now][i],now);
			Min[0][G[now][i]] = W[now][i];
		}
}

inline bool cmp(const int &a,const int &b){ return idx[a] < idx[b]; }

inline int Lca(int u,int v)
{
	if(dep[u] < dep[v]) swap(u,v);
	for(int i=0;dep[u] > dep[v];i++) if((1<<i) & (dep[u] - dep[v])) u = f[i][u];
	if(u==v) return u;
	for(int i=lim-1;i>=0;i--) if(f[i][u] != f[i][v]) u = f[i][u] , v = f[i][v];
	return f[0][u];
}

inline int cal(int u,int v)
{
	if(dep[u] < dep[v]) swap(u,v);
	int ret = 0x7f7f7f7f;
	for(int i=0;dep[u]-dep[v];i++)
		if((1<<i) & (dep[u] - dep[v]))
			ret = min(ret , Min[i][u]) , u = f[i][u];
	return ret;
}
LL dp[maxn];

void ser(int now)
{
	dp[now] = 0;
	for(int i=info[now];i;i=Prev[i])
	{
        ser(to[i]);
        if(is[to[i]]) dp[now]+=cal(now,to[i]);
		else dp[now]+=min(1ll*cal(now,to[i]) , dp[to[i]]);
	}
}

int main()
{
    scanf("%d",&n);
    for(int i=1,u,v,w;i<n;i++)
	{
		scanf("%d%d%d",&u,&v,&w);
        G[u].push_back(v) , G[v].push_back(u);
        W[u].push_back(w) , W[v].push_back(w);
        esiz[u]++,esiz[v]++;
	}

	dfs(1,0);
	for(int j=1;j<lim;j++)
		for(int i=1;i<=n;i++)
			f[j][i] = f[j-1][f[j-1][i]] , Min[j][i] = min(Min[j-1][i] , Min[j-1][f[j-1][i]]);

	int m,k;
	scanf("%d",&m);
	for(;m--;)
	{
		scanf("%d",&k);
		cnt_e = 0;
		for(;cdel;) info[del[--cdel]] = 0;
		for(int i=1;i<=k;i++) scanf("%d",&c[i]) , is[c[i]]=1;
		c[++k] = 1;
		sort(c+1,c+1+k,cmp);
		tp = 0;
		Q[tp++] = c[1];
        for(int i=2;i<=k;i++)
		{
			int lca = Lca(c[i] , Q[tp-1]);
			for(;tp>1 && dep[Q[tp-2]] > dep[lca];tp--) Node(Q[tp-2] , Q[tp-1]);
            if(dep[Q[tp-1]] > dep[lca]) Node(lca , Q[tp-1]) , tp--;
            if(dep[Q[tp-1]] < dep[lca]) Q[tp++] = lca;
            Q[tp++] = c[i];
		}
		for(;tp>1;tp--) Node(Q[tp-2],Q[tp-1]);
		ser(Q[tp-1]);
		printf("%lld\n",dp[Q[tp-1]]);
		for(int i=1;i<=k;i++) is[c[i]] = 0;
	}
}

虚树练习题:

[]消耗战

[]世界树

[]大工程

[ ]毒瘤

[ ]CF613D

[ ]战略游戏

[ ]寻宝游戏 (同上QAQ

[ ]CF809E

[ ]暴力写挂

[ ]通道

[ ]CF613D

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值