Problem F: tmk找三角
Description
有一棵树,树上有只tmk。他在这棵树上生活了很久,对他的构造了如指掌。所以他在树上从来都是走最短路,不会绕路。他还还特别喜欢三角形,所以当他在树上爬来爬去的时候总会在想,如果把刚才爬过的那几根树枝/树干锯下来,能不能从中选三根出来拼成一个三角形呢?
Input
第一行输入一个T,表示有多少组样例。
对于每组数据:第一行包含一个整数 N,表示树上节点的个数(从 1 到 N 标号)。
接下来的 N-1 行包含三个整数 a, b, len,表示有一根长度为 len 的树枝/树干在节点 a 和节点 b 之间。
接下来一行包含一个整数 M,表示询问数。
接下来M行每行两个整数 S, T,表示毛毛虫从 S 爬行到了 T,询问这段路程中的树枝/树干是否能拼成三角形。
Output
对于每组数据,每个询问输出一行,包含"Yes"或“No”,表示是否可以拼成三角形。
Sample Input
Sample Output
当时看到这道题的时候就蒙了,以为要用最短路而且要记录路径
后来发现根本不需要,因为这是一个树结构,这天杀的东西是没有环的!
所以我们要做的就是找到从s到t要经过的所有树的路径然后判断能不能构成三角形即可
至于如何判断能不能构成三角形,我们参见题解:
假设现在有 n 条线段,假设 n 条边从小到达排序,如果这 n 条边中没有三条可以构成三角形,那么这 n 条边必须满足关系:A[i] >= A[i-2]+A[i-1],这里的 A[i]表示第 i 条边的大小。假设 A[i]尽量取最小 A[i]=A[i-2]+A[i-1],且 A[0]=A[1]=1,是不是就是一个斐波那契,也就是对于一个 n 条边的集合,如果不存在三条边能构成一个三角形,那么最长的边至少为f[n-1],表示肥波纳妾第 n-1 项。而题目中 A[i]<1e9,也就是只要 n>50,就必定存在三条边可以构成一个三角形,所以我们只需要暴力加入两点路径上的边(如果大于
50,直接 Yes),然后对这些边进行排序,枚举第 i 条边为最长边,贪心判断 A[i]是否小于 A[i-1]+A[i-2]即可。
至于如何求从i到j的每条边呢,我们用了LCA和RMQ。我第一次见到这种东西等下回补一篇关于LCA和RMQ的博客
先放出注释详尽的代码
#include <stdio.h>
#include <cmath>
#include <algorithm>
#include <list>
#include <string.h>
using namespace std;
// 树的节点
struct Node
{
int next, len;
Node (int n, int l):next(n), len(l) {}
};
int pow2[20];
list<Node> nodes[100010];
bool visit[100010];
int ns[200010];
int nIdx;
int length[100010];
int parent[100010];
int depth[200010];
int first[100010];
int mmin[20][200010];
int edges[100010];
// DFS 对树进行预处理
void dfs(int u, int dep)
{
ns[++nIdx] = u; depth[nIdx] = dep;
visit[u] = true;
if (first[u] == -1) first[u] = nIdx;//first[u]存的是u的上一级
list<Node>::iterator it = nodes[u].begin(), end = nodes[u].end();
for (;it != end; it++)
{
int v = it->next;
if(!visit[v])
{
length[v] = it->len;
parent[v] = u;
dfs(v, dep + 1);
ns[++nIdx] = u;
depth[nIdx] = dep;
}
}
}
// 初始化 RMQ
void init_rmq()
{
nIdx = 0;
memset(visit, 0, sizeof(visit));
memset(first, -1, sizeof(first));
depth[0] = 0;
length[1] = parent[1] = 0;
//所有数组初始化为0,first初始化为-1
dfs(1, 1);
memset(mmin, 0, sizeof(mmin));
for(int i = 1; i <= nIdx; i++)
mmin[0][i] = i;
//最多有nidx个点
int t1 = (int)(log((double)nIdx) / log(2.0));
for(int i = 1; i <= t1; i++)
for(int j = 1; j + pow2[i] - 1 <= nIdx; j++)
{
int a = mmin[i-1][j], b = mmin[i-1][j+pow2[i-1]];
if(depth[a] <= depth[b]) mmin[i][j] = a;
else mmin[i][j] = b;
}
}
// RMQ 询问
int rmq(int u, int v)
{
int i = first[u], j = first[v];
if(i > j) swap(i, j);
int t1 = (int)(log((double)j - i + 1) / log(2.0));
int a = mmin[t1][i], b = mmin[t1][j - pow2[t1] + 1];
if(depth[a] <= depth[b]) return ns[a];
else return ns[b];
}
int main()
{
for(int i=0;i<20;i++)
pow2[i] = 1 << i;
int T, n, m, a, b, len;
scanf("%d ", &T);
for (int caseIdx = 1;caseIdx <= T;caseIdx++)
{
scanf("%d", &n);
for (int i = 0;i <= n;i++) nodes[i].clear();
for (int i = 1;i < n;i++)
{
scanf("%d%d%d", &a, &b, &len);
nodes[a].push_back(Node(b, len));
nodes[b].push_back(Node(a, len));
}//边的输入完成
init_rmq();
scanf("%d", &m);
//printf("Case #%d: ", caseIdx);
for (int i = 0;i < m;i++)
{
scanf("%d%d", &a, &b);
// 利用 RMQ 得到 LCA
int root = rmq(a, b);
bool success = false;
int l = 0;
//这里就是运用了并查集的思想
while (a != root)
{
edges[l++] = length[a];
a = parent[a];
}
while (b != root)
{
edges[l++] = length[b];
b = parent[b];
}
//所有的东西都放在这个集合里了接下来就按题解的方法搞就行了
if (l >= 3)
{
sort(edges, edges + l);
for (int j = 2;j < l;j++)
if (edges[j - 2] + edges[j - 1] > edges[j])
{success = true;break;}
}
if (success) puts("Yes");
else puts("No");
}
}
return 0;
}
653

被折叠的 条评论
为什么被折叠?



