广工ACM决赛_PF


Problem F: tmk找三角

Description

有一棵树,树上有只tmk。他在这棵树上生活了很久,对他的构造了如指掌。所以他在树上从来都是走最短路,不会绕路。他还还特别喜欢三角形,所以当他在树上爬来爬去的时候总会在想,如果把刚才爬过的那几根树枝/树干锯下来,能不能从中选三根出来拼成一个三角形呢?

Input

第一行输入一个T,表示有多少组样例。

对于每组数据:第一行包含一个整数 N,表示树上节点的个数(从 到 标号)。

接下来的 N-1 行包含三个整数 a, b, len,表示有一根长度为 len 的树枝/树干在节点 和节点 之间。

接下来一行包含一个整数 M,表示询问数。

接下来M行每行两个整数 S, T,表示毛毛虫从 爬行到了 T,询问这段路程中的树枝/树干是否能拼成三角形。

Output

对于每组数据,每个询问输出一行,包含"Yes"“No”,表示是否可以拼成三角形。

Sample Input

251 2 51 3 202 4 304 5 1523 43 551 4 322 3 1003 5 454 5 6021 41 3

Sample Output

NoYesNoYes

当时看到这道题的时候就蒙了,以为要用最短路而且要记录路径

后来发现根本不需要,因为这是一个树结构,这天杀的东西是没有环的!

所以我们要做的就是找到从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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值