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) )
*/
时间: 2024-10-07 05:17:57

BZOJ 2286 SDOI2011 消耗战 倍增LCA+单调栈的相关文章

bzoj-2286 消耗战【虚树+倍增lca+单调栈】

2286: [Sdoi2011消耗战 Time Limit: 20 Sec  Memory Limit: 512 MB Submit: 1815  Solved: 645 [Submit][Status][Discuss] Description 在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达.现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望.已知在其他k个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是

bzoj 2286 [Sdoi2011]消耗战(虚树+树上DP)

2286: [Sdoi2011]消耗战 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 1276  Solved: 445[Submit][Status][Discuss] Description 在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达.现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望.已知在其他k个岛屿上有丰富能源,为了防止敌军获取能源,我军的任务是炸

BZOJ 3611 HEOI2014 大工程 倍增LCA+单调栈+树形DP

题目大意:给定一棵树,m次询问,每次给出k个关键点,询问这k个点之间的两两距离和.最小距离和最大距离 n<=100W,m<=50000,Σk<=2*n 处理方法同2286 消耗战 地址见 http://blog.csdn.net/popoqqq/article/details/42493725 这个题的DP有些麻烦 因此我把要处理的节点单独拎出来做的DP 具体状态和转移见代码 #include <cstdio> #include <cstring> #includ

bzoj 2286: [Sdoi2011消耗战

1 #include<cstdio> 2 #include<iostream> 3 #define M 1000009 4 #define N 250009 5 #define ll long long 6 #define inf 1000000000000000000LL 7 #include<algorithm> 8 using namespace std; 9 int n,head[N],next[M],u[M],cnt,fa[N][22],deep[N],m,h

BZOJ 2286 [Sdoi2011]消耗战

题解:对询问点建立虚树 然后在虚树上Dp 每个点父边边权为这个点到根的边权最小值 一开始学了假的虚树 一开始竟然没想到父边边权可以这样赋 #include<iostream> #include<cstdio> #include<cstring> #include<vector> #include<queue> #include<algorithm> using namespace std; const int maxn=500009;

BZOJ 2286: [Sdoi2011消耗战 [DP 虚树]

传送门 题意: 删除价值和最小的边使得$1$号点与$k$个关键点不连通 一个树形DP...但是询问多次,保证总的关键点数为$O(n)$ 先说一下这个$DP$ $f[i]$表示子树$i$中的关键点与$1$不连通的最小价值 如果$i$是关键点则必须删除$i$到$1$的权值最小的边,否则$\sum f[child\ of\ i]$ 学了一下虚树...找不到别的资料啊只有别人的$Blog$ 试验了好多写法 貌似其中有好多带$Bug$的写法 最终定下了现在的版本应该是没大有问题的吧...明天再做两道虚树,

BZOJ 2791 Poi2012 Rendezvous 倍增LCA

题目大意:给定一棵内向森林,多次给定两个点a和b,求点对(x,y)满足: 1.从a出发走x步和从b出发走y步会到达同一个点 2.在1的基础上如果有多解,那么要求max(x,y)最小 3.在1和2的基础上如果有多解,那么要求min(x,y)最小 4.如果在1.2.3的基础上仍有多解,那么要求x>=y 因此那个x>=y是用来省掉SPJ的,不是题目要求- - 容易发现: 如果a和b不在同一棵内向树上,显然无解,否则一定有解 定义根为从一个点出发能走到的第一个环上点,如果a和b的根相同,则到达LCA是

BZOJ 3732 Network Kruskal+倍增LCA

题目大意:给定一个n个点m条边的无向连通图,k次询问两点之间所有路径中最长边的最小值 NOIP2013 货车运输,几乎就是原题...只不过最小边最大改成了最大边最小... 首先看到最大值最小第一反应二分答案 但是二分答案O(kmlogn)明显做不了 这里我们考虑最小生成树 先生成一棵最小生成树,然后每次询问利用倍增LCA求出路径上的最大权值即可 本蒟蒻居然把LCA写挂了... 而且样例还过了... 伤不起啊... 90%达成 剩下一道刷点啥呢... #include<cstdio> #incl

BZOJ 1787 AHOI2008 紧急集合 倍增LCA

题目大意:给定一棵树,多次询问到三个点距离之和最小的点和距离 首先易知到两个点距离之和最小的点一定在两点间的路径上 于是到三个点距离之和最小的点一定在两两之间路径的交点上 然后很容易就会知道这个交点一定是其中两个点的LCA(其实是我不会证) 此外为什么不会是三个点共同的LCA呢?因为三个点共同的LCA一定是至少一对点的LCA 证明略(其实我也不会证) 然后就是枚举两两之间的LCA 求一下距离 取最小即可 然后就是倍增LCA的问题了 我的倍增LCA怎么又挂了 还能不能写对了0.0 #include