点击打开链接
Description
有一棵树,树上有只tmk。他在这棵树上生活了很久,对他的构造了如指掌。所以他在树上从来都是走最短路,不会绕路。他还还特别喜欢三角形,所以当他在树上爬来爬去的时候总会在想,如果把刚才爬过的那几根树枝/树干锯下来,能不能从中选三根出来拼成一个三角形呢?
Input
第一行输入一个T,表示有多少组样例。
对于每组数据:第一行包含一个整数 N,表示树上节点的个数(从 1 到 N 标号)。
接下来的 N-1 行包含三个整数 a, b, len,表示有一根长度为 len 的树枝/树干在节点 a 和节点 b 之间。
接下来一行包含一个整数 M,表示询问数。
接下来M行每行两个整数 S, T,表示毛毛虫从 S 爬行到了 T,询问这段路程中的树枝/树干是否能拼成三角形。
Output
对于每组数据,每个询问输出一行,包含"Yes"或“No”,表示是否可以拼成三角形。
题解:
假设现在有 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]即可。
#include <cstdio>
#include <set>
#include <cstring>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn=100005;
int head[maxn];
int dis[maxn];
int dep[maxn];
int fa[maxn],ans[maxn];
struct Edge{
int v,w,nxt;
}edge[2*maxn];
int tol,tot;
int n,m;
void addedge(int u,int v,int w){
edge[tol].v=v;
edge[tol].w=w;
edge[tol].nxt=head[u];
head[u]=tol++;
}
void dfs(int u,int d){
dep[u]=d;
for(int i=head[u];i!=-1;i=edge[i].nxt){
int v=edge[i].v;
if(v==fa[u])continue;
fa[v]=u;
dis[v]=edge[i].w;
dfs(v,d+1);
}
}
bool solve(int u,int v){
tot=0;
memset(ans,0,sizeof(ans));
while(tot<=50&&u!=v){
if(dep[u]<dep[v]){
ans[++tot]=dis[v];
v=fa[v];
}
else{
ans[++tot]=dis[u];
u=fa[u];
}
}
if(tot>=50)return true;
else {
sort(ans+1,ans+1+tot);
for(int i=1;i<=tot-2;++i){
if(ans[i]+ans[i+1]>ans[i+2]){
return true;
}
}
}
return false;
}
void init(){
tol=0;
memset(head,-1,sizeof(head));
for(int i=0;i<=n;++i)
fa[i]=i;
}
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
init();
for(int i = 1; i < n; ++ i)
{
int x, y, l;
scanf("%d%d%d", &x, &y, &l);
addedge(x, y, l);
addedge(y, x, l);
}
dfs(1, 0);
scanf("%d", &m);
for(int i = 0; i < m; ++ i)
{
int x, y;
scanf("%d%d", &x, &y);
puts(solve(x, y) ? "Yes" : "No");
}
}
return 0;
}