bzoj 3611: [Heoi2014]大工程(虚树+树形DP)

3611: [Heoi2014]大工程

Time Limit: 60 Sec   Memory Limit: 512 MB
Submit: 1697   Solved: 718
[ Submit][ Status][ Discuss]

Description

国家有一个大工程,要给一个非常大的交通网络里建一些新的通道。 
我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上。 
在 2 个国家 a,b 之间建一条新通道需要的代价为树上 a,b 的最短路径。
 现在国家有很多个计划,每个计划都是这样,我们选中了 k 个点,然后在它们两两之间 新建 C(k,2)条 新通道。
现在对于每个计划,我们想知道:
 1.这些新通道的代价和
 2.这些新通道中代价最小的是多少 
3.这些新通道中代价最大的是多少

Input

第一行 n 表示点数。

 接下来 n-1 行,每行两个数 a,b 表示 a 和 b 之间有一条边。
点从 1 开始标号。 接下来一行 q 表示计划数。
对每个计划有 2 行,第一行 k 表示这个计划选中了几个点。
 第二行用空格隔开的 k 个互不相同的数表示选了哪 k 个点。

Output

输出 q 行,每行三个数分别表示代价和,最小代价,最大代价。 

Sample Input

10
2 1
3 2
4 1
5 2
6 4
7 5
8 6
9 7
10 9
5
2
5 4
2
10 4
2
5 2
2
6 1
2
6 1

Sample Output

3 3 3
6 6 6
1 1 1
2 2 2
2 2 2


把所有的点拉出来建棵虚树

另一种建树步骤:

①求出所有点的dfs序和深度d[],并对整棵树进行倍增LCA的预处理

②对于每次查询,用一个栈维护,先让根进栈,之后按照dfs序遍历当次查询的所有点

③对于当前点x,如果栈顶节点y是x的祖先,x进栈

④对于当前点x,如果栈顶节点y不是x的祖先,不停地弹栈并连边,直到栈顶两个元素y和y'满足y'不是y的祖先

⑤将lca与栈顶y连边(前提lca!=y)弹出栈顶,x进栈,继续执行步骤③

⑥所有点处理完毕之后栈中剩下的依次连边成一条链,搞定


之后就是简单的树形DP了

t[x]:节点x是否是本次查询的点(1or0)

siz[x]:以节点x为根的子树中要查询的点有多少个

mn[x]:与离x节点最近的查询点的距离

mx[x]:与离x节点最远的查询点的距离


#include<stdio.h>
#include<vector>
#include<algorithm>
using namespace std;
#define LL long long
vector<int> G[1000005], G2[1000005];
int Time, dfn[1000005], d[1000005], f[1000005][24], siz[1000005];
int n, m, ansl, ansr, h[1000005], t[1000005], st[1000005], mx[1000005], mn[1000005];
LL ans;
bool comp(int x, int y)
{
	if(dfn[x]<dfn[y])
		return 1;
	return 0;
}
void Sech(int u)
{
	int i, v, j;
	dfn[u] = ++Time;
	for(i=0;i<G[u].size();i++)
	{
		v = G[u][i];
		if(v==f[u][0])
			continue;
		f[v][0] = u;
		for(j=0;f[f[v][j]][j]!=0;j++)
			f[v][j+1] = f[f[v][j]][j];
		d[v] = d[u]+1;
		Sech(v);
	}
}
int LCA(int u, int v)
{
	int i;
	if(d[u]<d[v])
		swap(u, v);
	for(i=19;i>=0;i--)
	{
		if(d[f[u][i]]>=d[v])
			u = f[u][i];
	}
	if(u==v)
		return u;
	for(i=19;i>=0;i--)
	{
		if(f[u][i]!=f[v][i])
			u = f[u][i], v = f[v][i];
	}
	return f[u][0];
}
void DP(int u)
{
	int v, i;
	siz[u] = t[u];
	if(t[u])
		mx[u] = mn[u] = 0;
	else
		mx[u] = -2*n, mn[u] = 2*n;
	for(i=0;i<G2[u].size();i++)
	{
		v = G2[u][i];
		DP(v);
		siz[u] += siz[v];
		ans += (LL)siz[v]*(m-siz[v])*(d[v]-d[u]);
		ansl = max(ansl, mx[u]+mx[v]+d[v]-d[u]);
		ansr = min(ansr, mn[u]+mn[v]+d[v]-d[u]);
		mx[u] = max(mx[u], mx[v]+d[v]-d[u]);
		mn[u] = min(mn[u], mn[v]+d[v]-d[u]);
	}
}
int main(void)
{
	int i, x, y, q, cnt, top, lca;
	scanf("%d", &n);
	for(i=1;i<=n-1;i++)
	{
		scanf("%d%d", &x, &y);
		G[x].push_back(y);
		G[y].push_back(x);
	}
	d[1] = 1;
	Sech(1);
	scanf("%d", &q);
	while(q--)
	{
		scanf("%d", &m);
		for(i=1;i<=m;i++)
		{
			scanf("%d", &h[i]);
			t[h[i]] = 1;
		}
		if(m==1)
		{
			printf("0 0 0\n");
			continue;
		}
		sort(h+1, h+m+1, comp);
		top = 0, cnt = m;
		st[++top] = 1;
		for(i=1;i<=m;i++)
		{
			if(h[i]==1)
				continue;
			lca = LCA(st[top], h[i]);
			if(lca==st[top])
			{
				st[++top] = h[i];
				continue;
			}
			while(LCA(st[top-1], h[i])==lca)
			{
				G2[st[top-1]].push_back(st[top]);
				top--;
			}
			if(lca!=st[top])
			{
				h[++cnt] = lca;
				G2[lca].push_back(st[top]);
			}
			st[top] = lca;
			st[++top] = h[i];
		}
		while(top--)
			G2[st[top]].push_back(st[top+1]);
		ans = 0, ansl = -2*n, ansr = 2*n;
		DP(1);
		printf("%lld %d %d\n", ans, ansr, ansl);
		for(i=1;i<=cnt;i++)
			t[h[i]] = 0;
		G2[1].clear();
		for(i=1;i<=cnt;i++)
			G2[h[i]].clear();
	}
	return 0;
}
/*
18
1 2
2 3
2 4
2 7
7 8
7 9
4 5
5 6
1 10
10 11
10 12
10 13
12 14
12 15
14 16
14 17
17 18
1
7
7 5 11 12 13 16 18
*/


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值