「Luogu2495」 [SDOI2011]消耗战 虚树

Luogu P2495 [SDOI2011]消耗战

problem

Solution

苦思冥想稍作思考之后可以得到一个树形DP的方法:
\(w(u,v)\)表示u,v之间的边的权值,\(f[u]\)表示以\(u\)为根的子树(不含\(u\))中所有关键点与根断开的最小代价,则转移方程为:

\[f[u]=\begin{cases}w(u,v)&\text{如果v是关键点}\\min(w(u,v),dp[v])&\text{如果v非关键点}\end{cases} \]

复杂度为\(O(nm)\),显然不正确

对于每次询问可以建立一颗虚树,边权为关键点之间最小的权值

在这道题中,如果一个关键点\(u\)的祖先中有关键点\(v\),那么\(u\)是不需要加入虚树的,因为\(v\)一定需要断开,同时也就断开了\(u\)

Code

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <iostream>
#define maxn 250005
using namespace std;
typedef long long ll;

const ll inf=0x7f7f7f7f7f7f7f7f;
int n,m,k,h[maxn];
int dep[maxn],dfn[maxn],sign,prt[maxn][21],key[maxn];
ll dp[maxn],mnc[maxn][21];

struct edge
{
	int u,v,nxt;
	ll w;
};

namespace Ori
{
	edge g[maxn*2];
	int head[maxn],ecnt;
	void eADD(int u,int v,ll w)
	{
		g[++ecnt].u=u;g[ecnt].v=v;g[ecnt].w=w;g[ecnt].nxt=head[u];head[u]=ecnt;
	}
}

namespace New
{
	edge g[maxn];
	int head[maxn],ecnt;
	void eADD(int u,int v,ll w)
	{
		g[++ecnt].u=u;g[ecnt].v=v;g[ecnt].w=w;g[ecnt].nxt=head[u];head[u]=ecnt;
	}
	void Inti()
	{
		for(register int i=1;i<=ecnt;++i)
			g[i].u=g[i].v=g[i].nxt=0,g[i].w=0LL;
		ecnt=0;
	}
}

void dfs(int u,int fa,int depth,ll w)
{
	dep[u]=depth,prt[u][0]=fa,mnc[u][0]=w,dfn[u]=++sign;
	for(register int i=1;(1<<i)<=depth;++i)
	{
		prt[u][i]=prt[prt[u][i-1]][i-1];
		mnc[u][i]=min(mnc[u][i-1],mnc[prt[u][i-1]][i-1]);
	}
	for(register int i=Ori::head[u];i;i=Ori::g[i].nxt)
	{
		int v=Ori::g[i].v;
		if(v==fa)
			continue;
		dfs(v,u,depth+1,Ori::g[i].w);
	}
}

int LCA(int x,int y)
{
	if(dep[x]<dep[y])
		swap(x,y);
	for(register int i=20;i>=0;--i)
		if(dep[prt[x][i]]>=dep[y])
			x=prt[x][i];
	if(x==y)
		return x;
	for(register int i=20;i>=0;--i)
		if(prt[x][i]!=prt[y][i])
			x=prt[x][i],y=prt[y][i];
	return prt[x][0];
}

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

ll getmnc(int u,int v)
{
	ll re=inf;
	if(dep[u]<dep[v])
		swap(u,v);
	for(register int i=20;i>=0;--i)
		if(dep[prt[u][i]]>=dep[v])
			re=min(re,mnc[u][i]),u=prt[u][i];
	return re;
}

int top,stk[maxn];
void Build()
{
	for(register int i=1;i<=k;++i)
	{
		if(top==1)
		{
			stk[++top]=h[i];
			continue;
		}
		int lca=LCA(stk[top],h[i]);
		if(lca==stk[top])
			continue;
		while(top>1 && dfn[stk[top-1]]>=dfn[lca])
			New::eADD(stk[top-1],stk[top],getmnc(stk[top-1],stk[top])),--top;
		if(lca!=stk[top])
			New::eADD(lca,stk[top],getmnc(lca,stk[top])),stk[top]=lca;
		stk[++top]=h[i];
	}
	while(top-1)
		New::eADD(stk[top-1],stk[top],getmnc(stk[top],stk[top-1])),--top;
}

void DP(int u)
{
	dp[u]=0;
	if(!New::head[u])
		return;
	for(register int i=New::head[u];i;i=New::g[i].nxt)
	{
		int v=New::g[i].v;
		DP(v);
		if(key[v])
			dp[u]+=New::g[i].w;
		else
			dp[u]+=min(New::g[i].w,dp[v]);
	}
	New::head[u]=0;
}

int main()
{
	scanf("%d",&n);
	for(register int i=1;i<n;++i)
	{
		int u,v;
		ll w;
		scanf("%d%d%lld",&u,&v,&w);
		Ori::eADD(u,v,w),Ori::eADD(v,u,w);
	}
	dfs(1,0,1,inf);
	scanf("%d",&m);
	while(m--)
	{
		scanf("%d",&k);
		for(register int i=1;i<=k;++i)
		{
			scanf("%d",&h[i]);
			key[h[i]]=1;
		}
		sort(h+1,h+k+1,cmp);
		top=0;stk[++top]=1;
		New::Inti();
		Build();
		DP(1);
		printf("%lld\n",dp[1]);
		for(register int i=1;i<=k;++i)
			key[h[i]]=0;
	}
	return 0;
}

posted @ 2019-01-18 21:51  lizbaka  阅读(158)  评论(0编辑  收藏  举报