[BZOJ 4016] [Luogu P2993] [FJOI2014]最短路径树问题

洛谷传送门

BZOJ传送门

题目描述

给一个包含 n 个点, m 条边的无向连通图。从顶点 1 出发,往其余所有点分别走一次并返回。

往某一个点走时,选择总长度最短的路径走。若有多条长度最短的路径,则选择经过的顶点序列字典序最小的那条路径(如路径A为 1 , 32 , 11 ,路径 B 1 , 3 , 2 , 11 ,路径 B 字典序较小。注意是序列的字典序的最小,而非路径中节点编号相连的字符串字典序最小)。到达该点后按原路返回,然后往其他点走,直到所有点都走过。

可以知道,经过的边会构成一棵最短路径树。请问,在这棵最短路径树上,最长的包含 K 个点的简单路径长度为多长?长度为该最长长度的不同路径有多少条?

这里的简单路径是指:对于一个点最多只经过一次的路径。不同路径是指路径两端端点至少有一个不同,点 A 到点 B 的路径和点 B 到点 A 视为同一条路径。

输入输出格式

输入格式:

第一行输入三个正整数 n , m , K ,表示有 n 个点 m 条边,要求的路径需要经过K个点。

接下来输入 m 行,每行三个正整数 A i , B i , C i ( 1 A i , B i n , 1 C i 10000 ) ,表示 A i B i 间有一条长度为 C i 的边。

数据保证输入的是连通的无向图。

输出格式:

输出一行两个整数,以一个空格隔开,第一个整数表示包含K个点的路径最长为多长,第二个整数表示这样的不同的最长路径有多少条。

输入输出样例

输入样例#1:

6 6 4
1 2 1
2 3 1
3 4 1
2 5 1
3 6 1
5 6 1

输出样例#1:

3 4

说明

对于所有数据 n 30000 , m 60000 , 2 K n

数据保证最短路径树上至少存在一条长度为K的路径。

解题分析

既然要在最短路径树上 d p , 那么就先建树。 显然每个点从1开始跑的路径字典序最小, 我们就从1出发跑一遍最短路算出每个点到1号点的最短距离, 一个优先队列建出树。 然后就是比较裸的淀粉质啦。对于每棵子树, 我们统计在里面的点对于重心的相对深度, 然后就是一波讨论, 具体细节见代码。

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <cmath>
#include <queue>
#include <cstdlib>
#define R register
#define W while
#define gc getchar()
#define IN inline
#define MX 100005
#define INF 999999999
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;
}
std::queue <int> q;
std::priority_queue <int> qq;
int cnt, dot, line, k, deal, lef, rig, mx, root;
int head[MX], h[MX], dist[MX], fat[MX], dep[MX], siz[MX], sum[MX], que[MX], num[MX], val[MX];
long long ans1, ans2;
bool vis[MX];
struct Edge
{
    int to, len, nex;
}e[MX << 1], edge[MX << 1];
namespace SPFA
{
    IN void add(const int &from, const int &to, const int &len)
    {
        e[++cnt] = {to, len, h[from]};
        h[from] = cnt;
    }
    IN void addedge(const int &from, const int &to, const int &len)
    {
        edge[++cnt] = {to, len, head[from]};
        head[from] = cnt;
    }
    void spfa()
    {
        memset(dist, 63, sizeof(dist));
        R int now;
        q.push(1);
        dist[1] = 0;
        W (!q.empty())
        {
            now = q.front(); q.pop();
            for (R int i = h[now]; i; i = e[i].nex)
            {
                if(dist[e[i].to] >= dist[now] + e[i].len)
                {
                    if(dist[e[i].to] > dist[now] + e[i].len)
                    {
                        dist[e[i].to] = dist[now] + e[i].len;
                        if(!vis[e[i].to]) 
                        {
                            vis[e[i].to] = true;
                            q.push(e[i].to);
                        }
                    }
                }
            }
            vis[now] = false;
        }
    }
    void rebuild()
    {
        R int now;
        qq.push(-1); vis[1] = true;
        W (!qq.empty())
        {
            now = qq.top(), qq.pop(); now = -now;//优先队列编号大的在前面, 所以存成负数
            for (R int i = h[now]; i; i = e[i].nex)
            {
                if(vis[e[i].to] || dist[e[i].to] != dist[now] + e[i].len) continue;
                addedge(now, e[i].to, e[i].len), addedge(e[i].to, now, e[i].len), vis[e[i].to] = true, qq.push(-e[i].to);
            }
        }
    }
}
namespace Dot_Divide
{
    void reset() { for (R int i = 1; i <= k; ++i) val[i] = -INF, num[i] = 0; }
    void DFS(const int &now, const int &fa)
    {//统计子树大小
        siz[now] = 1;
        for (int i = head[now]; i; i = edge[i].nex)
        {
            if(edge[i].to == fa) continue;
            DFS(edge[i].to, now);
            siz[now] += siz[edge[i].to];
        }
    }
    void getroot(const int &now, const int &fa)
    {
        sum[now] = 0;
        for (R int i = head[now]; i; i = edge[i].nex)
        {
            if(vis[edge[i].to] || edge[i].to == fa) continue;
            getroot(edge[i].to, now);
            sum[now] = std::max(sum[now], siz[edge[i].to]);
        }
        sum[now] = std::max(sum[now], deal - siz[now]);
        if(sum[now] < mx) mx = sum[now], root = now;
    }
    void cal(const int &now)
    {
        int tar;
        for (R int j = head[now]; j; j = edge[j].nex)
        {
            if(vis[edge[j].to]) continue;
            dep[edge[j].to] = 1, dist[edge[j].to] = edge[j].len, fat[edge[j].to] = now;
            que[rig = 1] = edge[j].to, lef = 0;
            W (lef < rig)//手写队列存子树
            {
                ++lef; 
                for (R int i = head[que[lef]]; i; i = edge[i].nex)
                {
                    if(vis[edge[i].to] || edge[i].to == fat[que[lef]]) continue;
                    fat[edge[i].to] = que[lef], dep[edge[i].to] = dep[que[lef]] + 1, dist[edge[i].to] = dist[que[lef]] + edge[i].len;
                    if(dep[edge[i].to] >= k) break;
                    que[++rig] = edge[i].to;
                }
            }
            for (R int i = 1; i <= rig; ++i)//计算不同子树间的贡献
            {
                tar = dep[que[i]];
                if(dist[que[i]] + val[k - tar - 1] > ans1) ans1 = dist[que[i]] + val[k - tar - 1], ans2 = num[k - tar - 1];
                else if(dist[que[i]] + val[k - tar - 1] == ans1) ans2 += num[k - tar - 1];
            }
            for (R int i = 1; i <= rig; ++i)//更新最长路径长度和数量
            {
                tar = dep[que[i]];
                if(tar == k - 1)
                {
                    if(ans1 < dist[que[i]]) ans1 = dist[que[i]], ans2 = 1;//单独一条合法的链
                    else if(ans1 == dist[que[i]]) ans2++;
                    continue;
                }
                if(dist[que[i]] > val[tar]) val[tar] = dist[que[i]], num[tar] = 1;
                else if(dist[que[i]] == val[tar]) num[tar]++;
            }
        }
    }
    void solve(const int &now)
    {
        root = 0, mx = INF;
        getroot(now, 0);
        reset(); cal(root);
        vis[root] = true;
        for (int i = head[root]; i; i = edge[i].nex)
        {
            if(vis[edge[i].to]) continue;
            deal = siz[edge[i].to], solve(edge[i].to);
            //乍一看直接把处理的子树大小改成以1为根的子树大小有点问题, 其实这个问题只在子树中包含1节点的情况下存在,而多dfs一遍显然加大常数, 所以直接用此信息
        }
    }
}
int main(void)
{
    int a, b, c;
    in(dot), in(line), in(k);
    for (R int i = 1; i <= line; ++i)
    {
        in(a), in(b), in(c);
        SPFA::add(a, b, c), SPFA::add(b, a, c);
    }
    SPFA::spfa(), SPFA::rebuild(), Dot_Divide::DFS(1, 0);
    deal = dot;
    memset(vis, false, sizeof(vis));
    Dot_Divide::solve(1);
    printf("%lld %lld", ans1, ans2);
}

不过洛谷上数据真的水…博主建树曾只记录了前一个点的字典序, 并且solve函数的

for (int i = head[root]; i; i = edge[i].nex)

写成了

for (int i = head[now]; i; i = edge[i].nex)

居然还有80分….

猜你喜欢

转载自blog.csdn.net/lpa20020220/article/details/80545783