藏宝图 (treas)

背景

Czy 爬上黑红树, 到达了一个奇怪的地方……

题目描述

Czy 发现了一张奇怪的藏宝图. 图上有 n 个点, m 条无向边. 已经标出了图中两两之间距离 dist. 但是 czy 知道, 只有当图刚好又是一颗树的时候, 这张藏宝图才是真的. 如果藏宝图是真的, 那么经过点 x 的边的边权平均数最大的那个 x 是藏着宝物的地方. 请计算这是不是真 的藏宝图, 如果是真的藏宝之处在哪里.

格式

输入数据第一行一个数 T, 表示 T 组数据.

对于每组数据, 第一行一个 n, 表示藏宝图上的点的个数.

接下来 n 行, 每行 n 个数, 表示两两节点之间的距离.

输出一行或两行. 第一行 “Yes” 或 “No”, 表示这是不是真的藏宝图.

若是真的藏宝图, 第二行再输出一个数, 表示哪个点是藏宝之处.

样例输入

2
3
0 7 9
7 0 2
9 2 0
3
0 2 7
2 0 9
7 9 0

样例输出

Yes
1
Yes
3

样例解释

第一棵树的形状是 1--2--3. 1、2 之间的边权是 7, 2、3 之间是 2.

第二棵树的形状是 2--1--3. 2、1 之间的边权是 2, 1、3 之间是 7.

数据范围

对于 30% 数据,\(n \leq 50\),\(1 \leq 树上的边的长度 \leq 10^9\).

对于 50% 数据,\(n \leq 600\).

对于 100% 数据,\(1 \leq n \leq 2500\), 除 30% 小数据外任意 \(0 \leq dist[i][j] \leq 10^9\),\(T \leq 5\)

Explanation

此题看上去很玄学的样子。

先生成一个最小生成树, 我们可以证明: 若原图是一棵树, 则其最小生成树必定是其原图. 得到这个结论以后, 我们将原来的完全图 (当然是一个稠密图, 极其稠密的稠密图) 进行一下 生成最小生成树.

显然我们不能用 Kruskal, 因为在稠密图上被 Prim 秒杀; 另外不能用堆优化 Prim, 否则它会被化到 \(O(n^2 \cdot log(n))\); 所以最后只能用 Prim.

然后有一段很玄学的 \(O(n^2 \cdot log(n))\) 最短路代码写在底下.

最后交题的时候由于是在本地评测, 所以就闹出了一段笑话:

在隔壁的台式机上由于出题人 恶意卡常 (并且出题人强行立 flag 说不用开 long long 但是实际是需要的), 只能跑到 3s 左右. 在我的笔记本上大概 std 需要跑 31s, 然后 就只能强行调时限了.

我的笔记本
隔壁的台式机

Source Code


#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <queue>
#include <cstring>

using namespace std;
typedef long long lli;
const int maxn = 2600;
const lli infinit = 0x007f7f7f7f7f7f7fll;

class MinimumSpanTree
{
public:
    struct edge {
        int u, v; lli len;
        edge *next;
    } *edges[maxn], epool[2 * maxn];
    int n, root, ecnt;
    void addedge(int u, int v, lli len)
    {
        edge *p = &epool[++ecnt],
             *q = &epool[++ecnt];
        p->u = u; p->v = v; p->len = len;
        p->next = edges[u]; edges[u] = p;
        q->u = v; q->v = u; q->len = len;
        q->next = edges[v]; edges[v] = q;
        return ;
    }
    lli dist[maxn][maxn];
    lli dis[maxn]; // The distance used in eval() function.
    int from[maxn]; // The "from" as in SPFA, relaxed nodes
    bool visited[maxn]; // Whether visited or not
    void eval(void)
    {
        // Resetting initial settings
        for (int i = 1; i <= n; i++)
            visited[i] = false, dis[i] = infinit;
        dis[1] = 0;
        // Iterate exactly n times with i.
        for (int i = 1; i <= n; i++) {
            int p = -1;
            // Select previously nearest node
            for (int j = 1; j <= n; j++)
                if (!visited[j])
                    if (p <= 0 || dis[j] < dis[p])
                        p = j; // Assign better node
            if (p < 0)
                break; // Search failed
            visited[p] = true;
            addedge(from[p], p, dist[from[p]][p]);
            // Relaxing edges
            for (int j = 1; j <= n; j++)
                if (!visited[j] && dist[p][j] < dis[j]) {
                    dis[j] = dist[p][j];
                    from[j] = p;
                }
            continue;
        }
        return ;
    }
    void clear(void)
    {
        memset(edges, 0, sizeof(edges));
        memset(epool, 0, sizeof(epool));
        memset(dist, 0, sizeof(dist));
        memset(from, 0, sizeof(from));
        n = 0, ecnt = 0;
        return ;
    }
} graph;

class ShortestPath
{
public:
    struct edge {
        int u, v; lli len;
        edge *next;
    } *edges[maxn], epool[2 * maxn];
    int n, ecnt;
    void addedge(int u, int v, lli len)
    {
        edge *p = &epool[++ecnt];
        p->u = u; p->v = v; p->len = len;
        p->next = edges[u]; edges[u] = p;
        return ;
    }
    bool visited[maxn];
    lli dist[maxn][maxn];
    lli edge_sum[maxn];
    int edge_cnt[maxn];
    vector<int> vec; // Vector optimizing shortest path calculation
    void dfs(int p)
    {
        for (edge *ep = edges[p]; ep; ep = ep->next) {
            // Already visited
            if (visited[ep->v])
                continue;
            for (unsigned int j = 0; j < vec.size(); j++) {
                dist[ep->v][vec[j]] = dist[vec[j]][ep->v] = dist[vec[j]][p] + ep->len;
                edge_sum[p] += ep->len;
                edge_sum[ep->v] += ep->len;
                edge_cnt[p]++;
                edge_cnt[ep->v]++;
            }
            vec.push_back(ep->v);
            visited[ep->v] = true;
            dfs(ep->v);
        }
        return ;
    }
    void eval(void) {
        dfs(1); }
    void clear(void)
    {
        memset(edges, 0, sizeof(edges));
        memset(epool, 0, sizeof(epool));
        memset(visited, 0, sizeof(visited));
        memset(dist, 0, sizeof(dist));
        memset(edge_sum, 0, sizeof(edge_sum));
        memset(edge_cnt, 0, sizeof(edge_cnt));
        n = 0, ecnt = 0;
        vec.clear();
        return ;
    }
    bool cp_vis[maxn];
    void cp_dfs(int p)
    {
        cp_vis[p] = true;
        for (MinimumSpanTree::edge *ep = graph.edges[p]; ep; ep = ep->next)
            if (!cp_vis[ep->v]) {
                addedge(p, ep->v, ep->len);
                cp_dfs(ep->v);
            }
        return ;
    }
    void copy_edges(void)
    {
        memset(cp_vis, 0, sizeof(cp_vis));
        cp_dfs(1);
    }
} spfa;

int T, n, m;

int select_best(void)
{
    int best_val = 0,
        best_pos = 1;
    for (int i = 1; i <= n; i++) {
        if (spfa.edge_cnt[i] <= 0)
            continue;
        int tmp = spfa.edge_sum[i] / spfa.edge_cnt[i];
        if (tmp > best_val) {
            best_val = tmp;
            best_pos = i;
        }
    }
    return best_pos;
}

int main(int argc, char** argv)
{
    scanf("%d", &T);
    for (int idx = 1; idx <= T; idx++) {
        scanf("%d", &n);
        graph.clear();
        spfa.clear();
        graph.n = spfa.n = n;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                scanf("%lld", &graph.dist[i][j]);
        // Evaluating MST, complexity O(n^2)
        graph.eval();
        // Copying edges data from MST to shortest path
        spfa.copy_edges();
        // Evaluating SPFA, complexity O(n^2 * logn)
        spfa.eval();
        // Judging
        bool okay = true;
        for (int i = 1; i <= n && okay; i++)
            for (int j = 1; j <= n && okay; j++)
                if (spfa.dist[i][j] > graph.dist[i][j])
                    okay = false;
        printf("%s\n", okay ? "Yes" : "No");
        // Print the "Best Place for Treasure Hiding"
        if (okay)
            printf("%d\n", select_best());
    }
    return 0;
}