3611: [Heoi2014]大工程
Time Limit: 60 Sec Memory Limit: 512 MBSubmit: 1616 Solved: 688
[ Submit][ Status][ Discuss]
Description
Input
第一行 n 表示点数。
Output
输出 q 行,每行三个数分别表示代价和,最小代价,最大代价。
Sample Input
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
6 6 6
1 1 1
2 2 2
2 2 2
HINT
n<=1000000
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;
}