BZOJ 3251 树上三角形:LCA【构成三角形的结论】

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3251

题意:

  给你一棵树,n个节点,每个点的权值为w[i]。

  接下来有m个形如(p,a,b)的操作:

    (1)p == 0:

      问你在从a到b的路径上,有没有三个点满足:它们的权值大小可以构成三角形。

      如果有,输出'Y',否则输出'N'。

    (2)p == 1:

      将点a的权值w[a]改为b。

 

题解:

  首先考虑一个结论:

    因为构成三角形的充要条件是:对于三个数a < b < c,满足a + b > c。

    所以不能构成三角形的条件是:对于三个数a < b < c,满足a + b <= c。

    所以对于一个升序数列a[i],让a[i]的增加速度最小,则类似于斐波那契数列:1,1,2,3,5,8,13...

    又因为点权范围为[1,2^31-1],所以一个不能构成三角形的数列,最多有大约45个数字。

    即:对于数字个数 >= 50的数列,必定有三个数能构成三角形。

  

  所以先找出(a,b)的最近公共祖先act,然后顺着路径最多取50个数。

  然后sort一遍取出的数t[i],扫一遍有没有t[i-2]+t[i-1]>t[i]。

 

AC Code:

  1 #include <iostream>
  2 #include <stdio.h>
  3 #include <string.h>
  4 #include <algorithm>
  5 #include <vector>
  6 #define MAX_N 100005
  7 #define MAX_K 20
  8 
  9 using namespace std;
 10 
 11 int n,m;
 12 int w[MAX_N];
 13 int t[MAX_N];
 14 int dep[MAX_N];
 15 int par[MAX_K][MAX_N];
 16 vector<int> edge[MAX_N];
 17 
 18 void read()
 19 {
 20     scanf("%d%d",&n,&m);
 21     for(int i=1;i<=n;i++)
 22     {
 23         scanf("%d",&w[i]);
 24     }
 25     int a,b;
 26     for(int i=0;i<n-1;i++)
 27     {
 28         scanf("%d%d",&a,&b);
 29         edge[a].push_back(b);
 30     }
 31 }
 32 
 33 void dfs(int now,int p,int d)
 34 {
 35     dep[now]=d;
 36     par[0][now]=p;
 37     for(int i=0;i<edge[now].size();i++)
 38     {
 39         int temp=edge[now][i];
 40         if(temp!=p) dfs(temp,now,d+1);
 41     }
 42 }
 43 
 44 void init_lca(int root)
 45 {
 46     dfs(root,-1,0);
 47     for(int k=0;k+1<MAX_K;k++)
 48     {
 49         for(int i=1;i<=n;i++)
 50         {
 51             if(par[k][i]==-1) par[k+1][i]=-1;
 52             else par[k+1][i]=par[k][par[k][i]];
 53         }
 54     }
 55 }
 56 
 57 int lca(int a,int b)
 58 {
 59     if(dep[a]>dep[b]) swap(a,b);
 60     for(int k=0;k<MAX_N && dep[a]!=dep[b];k++)
 61     {
 62         if(((dep[b]-dep[a])>>k)&1)
 63         {
 64             b=par[k][b];
 65         }
 66     }
 67     if(a==b) return a;
 68     for(int k=MAX_K-1;k>=0;k--)
 69     {
 70         if(par[k][a]!=par[k][b])
 71         {
 72             a=par[k][a];
 73             b=par[k][b];
 74         }
 75     }
 76     return par[0][a];
 77 }
 78 
 79 bool check(int a,int b,int act)
 80 {
 81     int cnt=0;
 82     t[cnt++]=w[act];
 83     int now=a;
 84     while(now!=act)
 85     {
 86         t[cnt++]=w[now];
 87         now=par[0][now];
 88         if(cnt>50) break;
 89     }
 90     now=b;
 91     while(now!=act)
 92     {
 93         t[cnt++]=w[now];
 94         now=par[0][now];
 95         if(cnt>50) break;
 96     }
 97     sort(t,t+cnt);
 98     for(int i=2;i<cnt;i++)
 99     {
100         if((long long)t[i-2]+t[i-1]>t[i]) return true;
101     }
102     return false;
103 }
104 
105 void work()
106 {
107     init_lca(1);
108     int p,a,b;
109     for(int i=0;i<m;i++)
110     {
111         scanf("%d%d%d",&p,&a,&b);
112         if(p==0)
113         {
114             int act=lca(a,b);
115             if(check(a,b,act)) printf("Y\n");
116             else printf("N\n");
117         }
118         else w[a]=b;
119     }
120 }
121 
122 int main()
123 {
124     read();
125     work();
126 }

 

转载于:https://www.cnblogs.com/Leohh/p/7647701.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值