2013微软编程之美 资格赛C 树上的三角形

题目大意:给你一棵树,问你树中两点之间的最短路上的边集合中是否可以找到三条边,使得它们可以组成一个三角形。

节点数100000,询问数100000,边长的范围是[1,1000000000]。

思路:对于这道题,直接将两点之间的边求出来再判断肯定是不现实的(小数据就可以。。。),我们不妨先来求一个简化的问题,即:给你一系列边,问你从中是否可以选择三条边组成一个三角形。对于给定三条边a,b,c,它们能组成一个三角形当且仅当

1.a+b>c

2.b+c>a

3.a+c>b同时成立。

我们发现如果a<=b<=c,则只要满足a+b>c即可组成一个三角形(其他两个条件显然),所以我们不妨先对所给的边集按照非递减排一个序,设排序后的序列为a1,a2,a3...an,现在我们则是要在序列a中找到三条可以组成三角形的边。事实上,我们可以在O(n)的时间内,求出答案。我们从a3开始遍历,求最大边是ai时,是否可以找到两条边aj,ak使得j<i&&k<i&&k!=j&&aj+ak>ai。显然如果ai-1+ai-2<=ai(这里的ai-1指的是第i-1个数),则不存在以ai为最大边的三角形,否则,ai-2,ai-1,ai即为一个可行解,所以我们只要比较连续三个数的大小即可,时间复杂度是O(nlogn)。但是,对于n很大的情况我们还是无法在有效时间内得出解。

这时注意题目的要求,它是要求是否存在解,而不关心解是什么。通过刚才的分析我们可以知道,对于无解的情况,n一定不会很大,我们可以贪心求出n的最大值。

假设我们已经排好序,序列为b1,.....bn,则对于无解的情况,序列b要满足:

1:bi+bi+1<=bi+2。

2:bi<=bi+1。

因为我要n尽可能的大,所以第一个条件可以取==,我们现在开始构造b

前两个数显然是1,1,第三个数由第一个条件为2,第4个为1+2=3,第五个为2+3=5,。。。。。我们可以发现这其实就是斐波那契数列,我们可以求出对于第45个数时,它的值第一次超过1000000000,所以对于题目给出的数据范围,n最大为44,所以这就好求了。

对于两点间边数超过44的直接输出Yes,否则就暴力将这些边求出来再有上面讲的方法求即可,最大不过44条边,所以还是很快的。求两点之间的路径用LCA即可。下面附代码:

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <algorithm>
#define maxn 100010
using namespace std;
struct edge
{
    int to;
    int next;
    int len;
}e[maxn<<1];
int box[maxn],cnt,tot;
int siz[maxn],top[maxn],son[maxn],dep[maxn],fa[maxn],len[maxn];
void init()
{
    tot=0;
    son[0]=dep[0]=0;
    memset(box,-1,sizeof(box));
    cnt=0;
}
void add(int from,int to,int len)
{
    e[cnt].to=to;
    e[cnt].len=len;
    e[cnt].next=box[from];
    box[from]=cnt++;
}
void dfs(int now,int pre,int le)
{
    siz[now]=1;
    fa[now]=pre;
    len[now]=le;
    son[now]=0;
    dep[now]=dep[pre]+1;
    int t,v,l;
    for(t=box[now];t+1;t=e[t].next)
    {
        v=e[t].to,l=e[t].len;
        if(v!=pre)
        {
            dfs(v,now,l);
            siz[now]+=siz[v];
            if(siz[son[now]]<siz[v])
            {
                son[now]=v;
            }
        }
    }
}
void dfs2(int now,int tp)
{
    top[now]=tp;
    if(son[now])
    dfs2(son[now],top[now]);
    int t,v;
    for(t=box[now];t+1;t=e[t].next)
    {
        v=e[t].to;
        if(v!=fa[now]&&v!=son[now])
        dfs2(v,v);
    }
}
int LCA(int a, int b)
{
    while (1)
    {
        if (top[a] == top[b])
        return dep[a] <= dep[b] ? a : b;
        else if (dep[top[a]] >= dep[top[b]])
        a = fa[top[a]];
        else b = fa[top[b]];
    }
}
int aa[50];
int check(int num)
{
    if(num<3)
    return 0;
    sort(aa,aa+num);
    for(int i=0;i<num-2;i++)
    {
        if(aa[i]+aa[i+1]>aa[i+2])
        return 1;
    }
    return 0;
}
void solve(int a,int b,int c)
{
    int num=0;
    while(a!=c)
    {
        aa[num++]=len[a];
        a=fa[a];
    }
    while(b!=c)
    {
        aa[num++]=len[b];
        b=fa[b];
    }
    if(check(num))
    printf("Yes\n");
    else
    printf("No\n");
}
int main()
{
    //freopen("dd.txt","r",stdin);
    int ncase,time=0;
    scanf("%d",&ncase);
    while(ncase--)
    {
        printf("Case #%d:\n",++time);
        init();
        int n,m,i,a,b,c;
        scanf("%d",&n);
        for(i=1;i<n;i++)
        {
            scanf("%d%d%d",&a,&b,&c);
            add(a,b,c);
            add(b,a,c);
        }
        dfs(1,0,0);
        dfs2(1,1);
        scanf("%d",&m);
        while(m--)
        {
            scanf("%d%d",&a,&b);
            c=LCA(a,b);
            //printf("%d %d %d\n",a,b,c);
            int limit=dep[a]+dep[b]-2*dep[c];
            if(limit>44)
            printf("Yes\n");
            else
            {
                solve(a,b,c);
            }
        }
    }
    return 0;
}


你可能感兴趣的:(2013微软编程之美 资格赛C 树上的三角形)