3251: 树上三角形
Time Limit: 10 Sec Memory Limit: 128 MB
Submit: 1282 Solved: 521
[Submit][Status][Discuss]
Description
给定一大小为n的有点权树,每次询问一对点(u,v),问是否能在u到v的简单路径上取三个点权,以这三个权值为边
长构成一个三角形。同时还支持单点修改。
Input
第一行两个整数n、q表示树的点数和操作数
第二行n个整数表示n个点的点权
以下n-1行,每行2个整数a、b,表示a是b的父亲(以1为根的情况下)
以下q行,每行3个整数t、a、b
若t=0,则询问(a,b)
若t=1,则将点a的点权修改为b
n,q<=100000,点权范围[1,2^31-1]
Output
对每个询问输出一行表示答案,“Y”表示有解,“N”表示无解。
Sample Input
5 5
1 2 3 4 5
1 2
2 3
3 4
1 5
0 1 3
0 4 5
1 1 4
0 2 5
0 2 3
Sample Output
N
Y
Y
N
我们考虑如果数字不能组成三角形?
那么将是:1,2,3,5,8,… ,可以发现这是一个斐波那契数列。由于斐波那契的性质,大于等于47项时就超过1e9了,所以如果两点之间点的个数大于等于47直接Y,否则暴力判断。
统计两点间点的数量LCA即可。
AC代码:
#pragma GCC optimize(2)
#include<bits/stdc++.h>
//#define int long long
using namespace std;
const int N=1e5+10;
int n,q,a[N],d[N],f[N][20],h[N],lg[N];
int head[N],nex[N],to[N],tot;
inline void add(int a,int b){to[++tot]=b; nex[tot]=head[a]; head[a]=tot;}
void dfs(int x,int fa){
h[x]=h[fa]+1; f[x][0]=fa;
for(int i=1;(1<<i)<=h[x];i++) f[x][i]=f[f[x][i-1]][i-1];
for(int i=head[x];i;i=nex[i]){
d[to[i]]=d[x]+1; dfs(to[i],x);
}
}
inline int LCA(int x,int y){
if(h[x]<h[y]) swap(x,y);
while(h[x]>h[y]) x=f[x][lg[h[x]-h[y]]-1];
if(x==y) return x;
for(int i=lg[h[x]]-1;i>=0;i--)
if(f[x][i]!=f[y][i])
x=f[x][i],y=f[y][i];
return f[x][0];
}
inline int check(int x,int y){
int lca=LCA(x,y),cnt=d[x]+d[y]-2*d[lca]+1;
if(cnt>=47) return 1; vector<int> v; v.push_back(a[lca]);
while(x!=lca) v.push_back(a[x]),x=f[x][0];
while(y!=lca) v.push_back(a[y]),y=f[y][0];
sort(v.begin(),v.end());
for(int i=2;i<v.size();i++)
if(v[i]*1ll<v[i-1]*1ll+v[i-2]*1ll) return 1;
return 0;
}
signed main(){
cin>>n>>q;
for(int i=1;i<=n;i++) lg[i]=lg[i-1]+(1<<lg[i-1]==i);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1,a,b;i<n;i++) scanf("%d %d",&a,&b),add(a,b);
dfs(1,0);
while(q--){
int op,x,y; scanf("%d %d %d",&op,&x,&y);
if(op) a[x]=y;
else puts(check(x,y)?"Y":"N");
}
return 0;
}