[Luogu 4103] [BZOJ 3611] [HEOI 2016] 大工程

洛谷传送门
BZOJ传送门

题目描述

国家有一个大工程,要给一个非常大的交通网络里建一些新的通道。

我们这个国家位置非常特殊,可以看成是一个单位边权的树,城市位于顶点上。

2 2 个国家 a,b 之间建一条新通道需要的代价为树上 a,b a , b 的最短路径。

现在国家有很多个计划,每个计划都是这样,我们选中了 k k 个点,然后在它们两两之间 新建 C(k,2)条 新通道。现在对于每个计划,我们想知道: 1 1 .这些新通道的代价和 2.这些新通道中代价最小的是多少 3 3 .这些新通道中代价最大的是多少

输入输出格式

输入格式:

第一行 n 表示点数。

接下来 n1 n − 1 行,每行两个数 a,b a , b 表示 a a b 之间有一条边。点从 1 1 开始标号。

接下来一行 q 表示计划数。对每个计划有 2 2 行,第一行 k 表示这个计划选中了几个点。

第二行用空格隔开的 k k 个互不相同的数表示选了哪 k 个点。

输出格式:

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

输入输出样例

输入样例#1:

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

输出样例#1:

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

说明

对于第 1,2 个点: n10000

对于第 3,4,5 个点: n100000 n ≤ 100000 ,交通网络构成一条链

对于第 6,7 个点: n100000 n ≤ 100000

对于第 8,9,10 个点: n1000000 n ≤ 1000000

对于所有数据, q50000 q ≤ 50000 并且保证所有k之和 2n ≤ 2 ∗ n

解题分析

看到 ki2n ∑ k i ≤ 2 ∗ n 就知道应该是一道虚树题了…

对于每个询问, 我们用欧拉序建出虚树, 再模拟DFS过程。

对于求最大最小值, 我们DFS的时候不断在父节点处更新子树内两点间距离最大最小值即可。

稍微难搞一点的是代价和。 我们假设树被分为两部分 S1 S 1 S2 S 2 ,那么显然连接这两部分的那条边 A A 被计算的次数是size(S1)size(S2),既可以在DFS时一边爬树一边处理也可以处理出子树大小计算。

总复杂度还是 O(Nlog(N)) O ( N l o g ( N ) )

#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <limits.h>
#define R register
#define IN inline
#define gc getchar()
#define W while
#define MX 1005000
#define ll long long
template <class T>
IN void in(T &x)
{
    x = 0; R char c = gc;
    W (!isdigit(c)) c = gc;
    W (isdigit(c))
    x = (x << 1) + (x << 3) + c - 48, c = gc;
}
int dot, q, cnt, cot, top;
int fat[MX], son[MX], dep[MX], topf[MX], ein[MX],
eout[MX], sta[MX], tree[MX << 1], head[MX];
ll mn[MX], sum[MX], siz[MX], mx[MX], ansmx, ansmn, anstot;
bool inq[MX];
struct Edge
{
    int to, nex;
}edge[MX << 1];
IN void addedge(const int &from, const int &to)
{
    edge[++cnt] = {to, head[from]};
    head[from] = cnt;
}
IN bool cmp(const int &x, const int &y)
{
    int key1 = x > 0 ? ein[x] : eout[-x];
    int key2 = y > 0 ? ein[y] : eout[-y];
    return key1 < key2;
}
namespace LCA
{
    void DFS1(const int &now, const int &fa)
    {
        fat[now] = fa; ein[now] = ++cot; siz[now] = 1;
        for (R int i = head[now]; i; i = edge[i].nex)
        {
            if(edge[i].to == fa) continue;
            dep[edge[i].to] = dep[now] + 1;
            DFS1(edge[i].to, now);
            siz[now] += siz[edge[i].to];
            if(siz[edge[i].to] > siz[son[now]]) son[now] = edge[i].to;
        }
        eout[now] = ++cot;
    }
    void DFS2(const int &now, const int &grand)
    {
        topf[now] = grand;
        if(!son[now]) return;
        DFS2(son[now], grand);
        for (R int i = head[now]; i; i = edge[i].nex)
        {
            if(edge[i].to == fat[now] || edge[i].to == son[now]) continue;
            DFS2(edge[i].to, edge[i].to);
        }       
    }
    IN int query(R int x, R int y)
    {
        W (topf[x] != topf[y])
        {
            if(dep[topf[x]] < dep[topf[y]]) std::swap(x, y);
            x = fat[topf[x]];
        }
        return dep[x] < dep[y] ? x : y;
    }
}
int main(void)
{
    int a, b, num, bd, lca, now, fa, dis;
    in(dot);
    for (R int i = 1; i < dot; ++i)
    in(a), in(b), addedge(a, b), addedge(b, a);
    in(q); LCA::DFS1(1, 0); LCA::DFS2(1, 1);
    for (R int i = 1; i <= dot; ++i) mn[i] = INT_MAX;//如果不是关键点而是关键点的LCA的情况
    //距离最小值应该取子树中的最小值而不是0
    W (q--)
    {
        in(num); anstot = 0, ansmx = 0, ansmn = INT_MAX;
        for (R int i = 1; i <= num; ++i)
        in(tree[i]), mn[tree[i]] = 0, mx[tree[i]] = 0, siz[tree[i]] = 1, inq[tree[i]] = true;
        std::sort(tree + 1, tree + 1 + num, cmp); bd = num;
        for (R int i = 1; i < bd; ++i)
        {
            lca = LCA::query(tree[i], tree[i + 1]);
            if(!inq[lca]) tree[++num] = lca, inq[lca] = true, siz[lca] = 0;
        }
        bd = num;
        for (R int i = 1; i <= bd; ++i) tree[++num] = -tree[i];
        std::sort(tree + 1, tree + 1 + num, cmp);
        for (R int i = 1; i <= num; ++i)
        {
            if(tree[i] > 0) sta[++top] = tree[i];
            else
            {
                now = sta[top--]; fa = sta[top]; dis = dep[now] - dep[fa];
                if(top)//弹完的时候不必计算了
                {
                    sum[now] += dis * siz[now];//更新距离和,将新的一条边加入计算
                    anstot += sum[now] * siz[fa] + sum[fa] * siz[now];//更新总数
                    siz[fa] += siz[now], sum[fa] += sum[now];//更新父节点
                    mn[now] += dis; ansmn = std::min(ansmn, mn[now] + mn[fa]);
                    mx[now] += dis; ansmx = std::max(ansmx, mx[now] + mx[fa]);
                    mn[fa] = std::min(mn[fa], mn[now]); mx[fa] = std::max(mx[fa], mx[now]);
                }
                mx[now] = 0, mn[now] = INT_MAX, siz[now] = 0, sum[now] = 0, inq[now] = false;
                //注意清零
            }
        }
        printf("%lld %lld %lld\n", anstot, ansmn, ansmx);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值