CCF 201503-4 网络延时 传送门
初看题: 这不就是多源最短路径里长度最大的? floyd? 不过看数据,这样只能过70%的数据,正解肯定不是这样. 写了份floyd交上去, 70分. 这说明出题人特别仁慈, 给了这么多放水的数据, 可是这样是不是太不公平?
但是什么是正解呢?
1 ≤ n ≤ 10000,1 ≤ m ≤ 10000
, 这么大个数据,肯定得从题目的特殊性下手
思考一下可能跟最近公共祖先(LCA)有关, 因为这样的路径一定经过两个端点的LCA. 可通过倍增法求出两个叶子节点的最近公共祖先及距离公共祖先的长度, 然后相加, 找这些长度中最长的.
一对结点查找大概是logn的复杂度, 但是枚举所有可能的情况是O(n^2)
级别的复杂度. 综合起来就是O(n²logn)
, 这肯定是会超时的, 所以LCA也行不通.
那么还有什么算法? 动态规划?
其实看数据, 最差应该是O(nlogn)
, 然后最多可能会有个比较大的常数. 当然, 这是数据范围的限制.
画个图来理解. 既然这是一棵树, 我们就从树的特殊性来求解. 我们肯定知道所求的路径的两端一定是叶子节点, 他们向上查找祖先结点时一定会有公共祖先(但一般不会存在其中一个是另一个的祖先的情况). 我们思考一下这个公共祖先的性质, 会发现, 这条路径的长度就是从这个祖先分别到两个端点的路径长度和, 所以想到了什么? 答案就是所有的中间节点的最大的路径和.
一个结点可能会有两个以上子结点, 而这些路往下, 深度最大的两颗子树, 深度之和就是答案. 我们定义这样一个结构体
struct Node {
int first, second, degree, parent;
Node () {first = second = degree = parent = 0;}
};
一个结点存储了它的父节点, 入度, 最大子树深度和第二子树深度
. 我们只要把每个入度为0的结点压入队列中, 对每个出队的结点, 更新它父节点的相关信息(第一第二深度调整, 入度减一, 减到0就入队
), 这样当队列为空时, 树的深度遍历结束, 答案就是所有节点里面first + second
最大的.
100分代码, 自己都屁福
自己
#include <iostream>
#include <queue>
using namespace std;
struct Node {
int first, second, degree, parent;
Node () {first = second = degree = parent = 0;}
};
const int maxn = 20000 + 5;
Node nodes[maxn];
int n, m, ans = -1;
int main()
{
cin >> n >> m;
for (int i = 2, u; i <= n + m; ++i) { //可以合起来输入!
cin >> u;
nodes[u].degree++;
nodes[i].parent = u;
}
queue<int> Q;
for (int i = 1; i <= n + m; ++i)
if (nodes[i].degree == 0) Q.push(i);
while (!Q.empty()) {
int now = Q.front();
Q.pop();
if (now == 1) continue;
int next = nodes[now].first + 1, parent = nodes[now].parent;
if (nodes[parent].first <= next) {
swap(nodes[parent].first, nodes[parent].second);
nodes[parent].first = next;
} else if (nodes[parent].second < next) {
nodes[parent].second = next;
}
nodes[parent].degree--;
if (nodes[parent].degree == 0) Q.push(parent);
}
for (int i = 1; i <= n + m; ++i)
ans = max(ans, nodes[i].first + nodes[i].second);
cout << ans;
}
但是我百度了下, 发现几乎没有用我这种方法的, 他们用的是一种可以称之为算法的东西: 树的直径. 没错, 这道题就是求树的直径.
有这么一条定理
定理:假设路径s-t为树的直径,则从任意一点u出发找到的最远的点一定是s、t中的一点。
具体证明和分析可以参考这篇博客: 点我点我
做法就是选一个点, 从这个点BFS或DFS到最远的一个点u, 然后从这个u点BFS或DFS到另一个点v, 那么u->v
的长度就是树的直径, 也就是此题答案.
100分代码.
#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
const int maxn = 20000 + 5;
vector<int> G[maxn];
bool vis[maxn] = {};
int n, m, ans = 0, depth[maxn];
int main()
{
cin >> n >> m;
for (int i = 2, u; i <= n + m; ++i) {
cin >> u;
G[u].push_back(i);
G[i].push_back(u);
}
queue<int> Q;
Q.push(1);
vis[1] = true;
int now;
while (!Q.empty()) {
now = Q.front();
Q.pop();
for (int i = 0; i < G[now].size(); ++i) {
int v = G[now][i];
if (vis[v]) continue;
vis[v] = true;
Q.push(v);
}
}
Q.push(now);
memset(vis, false, sizeof(vis));
vis[now] = true;
while (!Q.empty()) {
now = Q.front();
Q.pop();
for (int i = 0; i < G[now].size(); ++i) {
int v = G[now][i];
if (vis[v]) continue;
vis[v] = true;
depth[v] = depth[now] + 1;
Q.push(v);
}
}
cout << depth[now];
}