【BZOJ2286】【SDOI2011】—消耗战(虚树)

传送门

虚树练手题


有一个显然的 O ( m n ) O(mn) O(mn) d p : dp: dp

我们考虑对于每一个点处理出两个东西

f [ i ] : f[i]: f[i]:表示点 i i i到根节点中最小的边

g [ i ] : g[i]: g[i]:表示将 i i i的子树中关键点全部切断的最小花费,如果子树没有关键点则为0

显然每个点的答案就是 m i n ( f [ i ] , g [ i ] ) min(f[i],g[i]) min(f[i],g[i])

一次 d p O ( n ) dpO(n) dpO(n)


接下来考虑如何优化

我们可以发现每次边权是不会变的

也就是说 f f f是不会变的

变的只是必须要切断的点

考虑到 Σ k i &lt; = 500000 \Sigma k_i&lt;=500000 Σki<=500000

我们把每次会对答案造成影响的点单独拿出来 d p dp dp

那么现在的问题就是怎么样找到会对答案造成影响的点

做法摘自自为风月马前卒

考虑得到了询问点,如何构造出一棵虚树。

首先我们要先对整棵树 d f s dfs dfs一遍,求出他们的 d f s dfs dfs序,然后对每个节点以 d f s dfs dfs序为关键字从小到大排序

同时维护一个栈,表示从根到栈顶元素这条链

假设当前要加入的节点为 p p p,栈顶元素为 x = s [ t o p ] x=s[top] x=s[top] l c a lca lca为他们的最近公共祖先
因为我们是按照 d f s dfs dfs序遍历,因此 l c a lca lca不可能是 p p p

那么现在会有两种情况:

l c a lca lca x x x,直接将 p p p入栈。

x , p x,p x,p分别位于 l c a lca lca的两棵子树中,此时 x x x这棵子树已经遍历完毕,(如果没有,即 x x x的子树中还有一个未加入的点 y y y,但是 d f n [ y ] &lt; d f n [ p ] dfn[y]&lt;dfn[p] dfn[y]<dfn[p],即应先访问 y y y), 我们需要对其进行构建
设栈顶元素为 x x x,第二个元素为 y y y

d f n [ y ] &gt; d f n [ l c a ] dfn[y]&gt;dfn[lca] dfn[y]>dfn[lca]可以连边 y &gt; x y&gt;x y>x,将 x x x出栈;

d f n [ y ] = d f n [ l c a ] dfn[y]=dfn[lca] dfn[y]=dfn[lca] y = l c a y=lca y=lca连边 l c a &gt; x lca&gt;x lca>x,此时子树构建完毕 ( b r e a k ) (break) (break)

d f n [ y ] &lt; d f n [ l c a ] dfn[y]&lt;dfn[lca] dfn[y]<dfn[lca] l c a lca lca y , x y,x y,x之间,连边 l c a &gt; x lca&gt;x lca>x x x x出栈,再将 l c a lca lca入栈。此时子树构建完毕 ( b r e a k ) (break) (break)

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	char ch=getchar();
	int res=0,f=1;
	while(!isdigit(ch)){if(ch=='-')f=-f;ch=getchar();}
	while(isdigit(ch))res=(res<<3)+(res<<1)+(ch^48),ch=getchar();
	return res*f;
}
#define int long long
const int N=250005;
int n,m,adj[N],nxt[N<<1],cnt,to[N<<1];
int f[N],a[N],val[N<<1];
int fa[N],dep[N],siz[N],son[N],topf[N],tot,dfn[N],stk[N],top;
vector<int> e[N];
inline void addedge(int u,int v,int w){
	nxt[++cnt]=adj[u],adj[u]=cnt,to[cnt]=v,val[cnt]=w;
}
inline void add(int u,int v){
	e[u].push_back(v);
}
void dfs1(int u){
	siz[u]=1;
	for(int e=adj[u];e;e=nxt[e]){
		int v=to[e];
		if(v==fa[u])continue;
		dep[v]=dep[u]+1,fa[v]=u;
		f[v]=min(f[u],val[e]);
		dfs1(v),siz[u]+=siz[v];
		if(siz[v]>siz[son[u]])son[u]=v;
	}
}
void dfs2(int u,int tp){
	topf[u]=tp,dfn[u]=++tot;
	if(!son[u])return;
	dfs2(son[u],tp);
	for(int e=adj[u];e;e=nxt[e]){
		int v=to[e];
		if(v==son[u]|v==fa[u])continue;
		dfs2(v,v);
	}
}
inline int Lca(int u,int v){
	while(topf[u]!=topf[v]){
		if(dep[topf[u]]<dep[topf[v]])v=fa[topf[v]];
		else u=fa[topf[u]];
	}
	if(dep[u]<dep[v])swap(u,v);
	return v;
}
inline void insert(int u){
	if(top==1){stk[++top]=u;return;}
	int lca=Lca(u,stk[top]);
	if(lca==stk[top])return;
	while(top>1&&dfn[stk[top-1]]>=dfn[lca])add(stk[top-1],stk[top]),--top;
	if(lca!=stk[top])add(lca,stk[top]),stk[top]=lca;
	stk[++top]=u;
}
inline bool comp(int a,int b){
	return dfn[a]<dfn[b];
}
inline int dp(int u){
	if(e[u].size()==0)return f[u];
	int res=0;
	for(int i=0;i<e[u].size();i++)
		res+=dp(e[u][i]);
	e[u].clear();
	return min(res,f[u]);
}
signed main(){
	f[1]=1ll<<50;
	n=read();
	for(int i=1;i<n;i++){
		int u=read(),v=read(),w=read();
		addedge(u,v,w),addedge(v,u,w);
	}
	dfs1(1),dfs2(1,1);
	m=read();
	for(int i=1;i<=m;i++){
		int p=read();
		for(int i=1;i<=p;i++)a[i]=read();
		sort(a+1,a+p+1,comp);
		stk[top=1]=1;
		for(int i=1;i<=p;i++)insert(a[i]);
		 while(top > 0)  add(stk[top - 1], stk[top]), top--;
		cout<<dp(1)<<'\n';
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值