首页 > 代码库 > BZOJ 2286 SDOI2011 消耗战 倍增LCA+单调栈

BZOJ 2286 SDOI2011 消耗战 倍增LCA+单调栈

题目大意:给定一棵树,边上有边权,m次询问,每次选定一些关键点,求将1号节点与所有关键点都切断所需的最小花销

关键点的总数<=50W

首先我们考虑暴力想法

令f[x]表示切断以x为根的子树中所有关键点的最小花销

g[x]表示x是不是关键点

那么对于x的每个子节点y有f[x]=Σmin(g[y]?INF:f[y],Distance(x,y) )

这样每次暴力做一遍树形DP,时间复杂度是O(n*m)的

现在由于每次询问的点数不一定会达到n的级别,对所有节点进行DFS非常浪费

我们可以将询问的关键点拿出来,单独模拟一次DFS

维护一个栈,栈中的元素形成一条由根节点出发的链,初始栈中只有根节点

将所有关键点按照DFS序排序

每次加入一个节点,求出节点与栈顶的LCA,将栈中所有深度大于LCA的节点全都弹掉

然后将LCA和该节点入栈,注意有些重复的情况要考虑

在这个模拟的DFS过程中顺便把DP做了即可

记得开long long

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define M 250100
#define INF 0x3f3f3f3fll
using namespace std;
struct abcd{
	int to,f,next;
}table[M<<1];
int head[M],tot;
int m,n,a[M];
int pos[M],dpt[M],fa[M][20],dis[M][20];
void Add(int x,int y,int z)
{
	table[++tot].to=y;
	table[tot].f=z;
	table[tot].next=head[x];
	head[x]=tot;
}
void DFS(int x)
{
	static int cnt=0;
	int i;
	pos[x]=++cnt;
	dpt[x]=dpt[fa[x][0]]+1;
	for(i=head[x];i;i=table[i].next)
		if(table[i].to!=fa[x][0])
		{
			fa[table[i].to][0]=x;
			dis[table[i].to][0]=table[i].f;
			DFS(table[i].to);
		}
}
bool Compare(int x,int y)
{
	return pos[x] < pos[y];
}
int LCA(int x,int y)
{
	int j;
	if(dpt[x]<dpt[y])
		swap(x,y);
	for(j=19;~j;j--)
		if(dpt[fa[x][j]]>=dpt[y])
			x=fa[x][j];
	if(x==y) return x;
	for(j=19;~j;j--)
		if(fa[x][j]!=fa[y][j])
			x=fa[x][j],y=fa[y][j];
	return fa[x][0];
}
int Distance(int x,int y)
{
	int j,re=0x3f3f3f3f;
	for(j=19;~j;j--)
		if(dpt[fa[x][j]]>=dpt[y])
			re=min(re,dis[x][j]),x=fa[x][j];
	return re;
}
int main()
{
	int i,j,k,x,y,z;
	cin>>n;
	for(i=1;i<n;i++)
	{
		scanf("%d%d%d",&x,&y,&z);
		Add(x,y,z);
		Add(y,x,z);
	}
	DFS(1);
	
	for(j=1;j<=19;j++)
		for(i=1;i<=n;i++)
			fa[i][j]=fa[fa[i][j-1]][j-1],
			dis[i][j]=min(dis[fa[i][j-1]][j-1],dis[i][j-1]);

	static int g[M],stack[M],top;
	static long long f[M];
	cin>>m;
	for(i=1;i<=m;i++)
	{
		scanf("%d",&k);
		for(j=1;j<=k;j++)
			scanf("%d",&a[j]);
		sort(a+1,a+k+1,Compare);
		stack[++top]=1;
		f[1]=0;g[1]=0;
		for(j=1;j<=k;j++)
		{
			int lca=LCA(stack[top],a[j]);
			while(dpt[stack[top]]>dpt[lca])
			{
				if(dpt[stack[top-1]]<=dpt[lca])
				{
					int temp=min(g[top]?INF:f[top],(long long)Distance(stack[top],lca) );
					stack[top--]=0;
					if(lca!=stack[top])
					{
						stack[++top]=lca;
						f[top]=0;g[top]=0;
					}
					f[top]+=temp;
					break;
				}
				else
				{
					f[top-1]+=min(g[top]?INF:f[top],(long long)Distance(stack[top],stack[top-1]) );
					stack[top--]=0;
				}
			}
			if(stack[top]!=a[j])
			{
				stack[++top]=a[j];
				f[top]=0;
			}
			g[top]=1;
		}
		while(top>1)
		{
			f[top-1]+=min(g[top]?INF:f[top],(long long)Distance(stack[top],stack[top-1]) );
			stack[top--]=0;
		}
		printf("%lld\n",f[top--]);
	}
	return 0;
}
/*
f[x]表示切断以x为根的子树中所有关键点的最小花销
g[x]表示x是不是关键点
f[x]=Σmin(g[table[i].to]?INF:f[table[i].to],Distance(x,table[i].to) )
*/


BZOJ 2286 SDOI2011 消耗战 倍增LCA+单调栈