bzoj3611大工程 虚树+树型动规

3611: [Heoi2014]大工程

Time Limit: 60 Sec   Memory Limit: 512 MB
Submit: 1616   Solved: 688
[ 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

HINT

n<=1000000 


q<=50000并且保证所有k之和<=2*n 

Source


题目大意:给一棵树,边权都是1,求任意k个点两两距离和、最小最大值
看到Σk<=n << 1就知道一定是虚树的题目,然而树型动规却想不出来,TT
膜拜各种题解之后总算是明白怎么个动规法。
先把虚树建出来
对于距离和,设f[i]为子树各个工程到根的距离和。那么f[u] = sum(f[v] + size[v] * w[v])其中v为u的儿子,size表示工程数目,w表示儿子父边权值(新建虚树上的边权,做的时候用deep减一下可以得出)统计的时候,每统计一颗子树就更新总距离,更新方式是tot += (f[u] + size[u] * e2[i].w) * size[v] + f[v] * size[u],意思就是u之前所有子树的工程到v的距离和加上v子树的所有工程到v的距离。好像点分治有木有
然后就是最大最小值。一样地,mn[i],mx[i]表示从所有子树到根i的最小最大值。那么显然,如果i本身是一个工程,那么最小最大值初值都可以为0,否则i必须从其他的工程走到自己才能计入答案。更新的时候就是mn/mx[i] = min/max(mn[i]/mx[i], mn[v] + w[v]),就是从别的子树走到自己, 更新总答案的时候mnans/mxans = min/max(mnans/mxans, mn[u]/mx[u] + mn[v]/mx[v] + w[v])也就是之前子树到根的答案与现在子树到根的答案。
总结一下这道题,其实建虚树很显然很暴力,关键还是树dp,而树dp的做法真的超级像点分治,个人觉得这题点分治也能做,树dp写出来也很像是点分治的做法。
最后注意要区别这个点在虚树中是否为选中的点。

#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<map>
#include<cmath>
#include<set>
#define maxn 1000005
#define inf 2000000000
using namespace std;
int read() {
	char ch = getchar(); int x = 0, f = 1;
	while(ch < '0' || ch > '9') {if(ch == '-') f = -1; ch = getchar();}
	while(ch >= '0' && ch <= '9') {x = x * 10 + ch - '0'; ch = getchar();}
	return x * f;
}

int pre[maxn][2], top, deep[maxn];
struct edge {
	int to, next, w;
	void add(int u, int v, bool p)
	{
		if(u == v) return;
		to = v; next = pre[u][p];
		pre[u][p] = top; if(p) w = deep[v] - deep[u];
	}
}e[maxn * 2], e2[maxn * 2];
void adds(int v, int u) {
	e[++top].add(u, v, 0);
	e[++top].add(v, u, 0);
} 
int fa[maxn], son[maxn], dson[maxn], d[maxn], in[maxn];
int cnt, n, m, Q, h[maxn], st[maxn];
long long mxans, mnans, tot, f[maxn], mn[maxn], mx[maxn], size[maxn];
bool vis[maxn];
bool cmp(int x, int y) {return in[x] < in[y];}

void dfs1(int u, int pa) {
	fa[u] = pa; deep[u] = deep[pa] + 1; son[u] = 1; dson[u] = 0;
	for(int i = pre[u][0]; i; i = e[i].next) {
		if(e[i].to == pa) continue;
		int v = e[i].to;
		dfs1(v, u);
		son[u] += son[v];
		if(son[v] > son[dson[u]]) dson[u] = v;
	}
}

void dfs2(int u, int chain) {
	in[u] = ++tot; d[u] = chain;
	if(!dson[u]) return;
	dfs2(dson[u], chain);
	for(int i = pre[u][0]; i; i = e[i].next) 
		if(e[i].to != dson[u] && e[i].to != fa[u])
			dfs2(e[i].to, e[i].to);
}

int lca(int u, int v) {
	while(d[u] != d[v]) {
		if(deep[d[u]] < deep[d[v]]) swap(u, v);
		u = fa[d[u]];
	}
	if(in[u] > in[v]) swap(u, v); 
	return u;
}

void init() {
	n = read();
	for(int i = 1;i < n; ++i) adds(read(), read());
	dfs1(1, 0); dfs2(1, 1);
}

void Itree_build() {
	m = read();
	for(int i = 1;i <= m; ++i) vis[h[i] = read()] = 1;
	sort(h + 1, h + m + 1, cmp); top = 0;
	st[++cnt] = 1;
	for(int i = 1;i <= m; ++i) {
		int cur = h[i], pa = lca(cur, st[cnt]);
		if(pa == st[cnt]) st[++cnt] = cur;
		else {
			while(pa == lca(cur, st[cnt - 1])) {
				e2[++top].add(st[cnt - 1], st[cnt], 1);
				pa = lca(cur, st[--cnt]);
			}
			e2[++top].add(pa, st[cnt], 1);
			st[cnt] = pa; st[++cnt] = cur;
		}
	}
	while(--cnt) e2[++top].add(st[cnt], st[cnt + 1], 1);
}

void dp(int u) {
	size[u] = vis[u]; f[u] = 0;
	mn[u] = vis[u] ? 0 : inf;
	mx[u] = vis[u] ? 0 : -inf;
	for(int i = pre[u][1]; i; i = e2[i].next) {
		int v = e2[i].to;
		dp(v);
		tot += (f[u] + size[u] * e2[i].w) * size[v] + f[v] * size[u];
		f[u] += f[v] + size[v] * e2[i].w;
		size[u] += size[v];
		mnans = min(mnans, mn[u] + mn[v] + e2[i].w);
		mn[u] = min(mn[u], mn[v] + e2[i].w);
		mxans = max(mxans, mx[u] + mx[v] + e2[i].w);
		mx[u] = max(mx[u], mx[v] + e2[i].w);
	}
	pre[u][1] = 0;
}

void solve() {
	Itree_build();
	tot = 0; mnans = inf; mxans = -inf;
	dp(1);
	printf("%lld %lld %lld\n", tot, mnans, mxans);
	for(int i = 1;i <= m; ++i) vis[h[i]] = 0;
}

int main()
{
	init();
	Q = read();
	for(int i = 1;i <= Q; ++i) 
		solve();
	return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值