Problem
给定一个n个点的以1为根的树,每个点有一个正整数点权。
有q个操作,每个操作为以下类型之一:
1. 1 u v 询问树上所有在u到v的简单路径的节点(含u,v)中,是否存在三个不同的节点,使得以这三个节点的点权为边长的三条边能够构成一个三角形。
2. 2 u v 将节点u的权值改成v。
3. 3 u v 若节点v不在以节点u为根的子树里,那么令u的父节点为v,否则令v的父节点为u,如果u=v那么忽略这一条操作。
所有操作按输入的顺序进行。
这个题面应该不会看不懂吧。。
Hint
对于20%的数据,保证n,q<=1000
对于另外30%的数据,保证对于每个操作,t<=2
对于再另外的30%的数据,保证询问操作不超过20次。
对于100%的数据,保证n<=100000 q<=200000 每个节点的权值<=(2^31)-1
Solution
这道题看上去很诡,是不是?
一开始,我看到“能够构成一个三角形”就懵逼了,完全不知道怎么做(没有见过这种套路),并且哀怨地想着:如果它是链信息求和或者子树信息求和之类的,我就直接打个LCT切了。
那么,我们先来思考一下没有3操作该怎么破。
50points:暴力+暴力lca
首先,设u到v的路径上的点的点权构成的序列为d,设路径长度为d[0]。将d从小到大排序,那么若它不能构成三角形,则必须满足:
。因为对于d[i+1]而言,它最接近且比它大的权值肯定是d[i+2];而在所有比它小的点权当中,d[i]又是最大的。所以如果d[i+1]可以是某个三角形中的第二长的边,那么d[i]+d[i+1]>d[i+2]。
那么,如果我们在最大权值的限定下,要使这个序列尽量地长,那肯定要使它满足:
。我们发现这就是个斐波那契数列。大概是第50项以后,斐波那契的值就会>(2^31)-1,也即超过最大权值的限定。所以,如果那个序列长度>50,那肯定会有某个i满足d[i]+d[i+1]>d[i+2],所以肯定可以构成三角形。
根据上述推论,如果u到v的路径上有超过50个点,那直接判可以。
所以,我们可以dfs一遍整棵树,然后对于某个询问,我们直接暴力lca,长度一旦超过50就直接退出,否则排序完扫一遍判断。
然后我们发现这其实就是BZOJ3251。(出题人真会抄袭别人的idea)(切完顺便切掉bzoj的那道题)
时间复杂度:前20points
,后30points
。
100points:LCT or splay
这道题LCT比较直观,我打的也是LCT。不会LCT的童鞋戳这里。
对于询问操作,我们先让u作为根,access(v),使u、v在一棵splay上,则此时u为该splay上键值最小的点,v为键值最大的点;而又由于平衡树是满足二叉排序树的性质的(右儿子>根>左儿子),所以这棵splay即为u到v的路径。直接在上面dfs求出里面所有的点,如果点数超过50则果断return。
对于单点修改操作,直接修改即可。
对于换父亲操作,这里我们碰到了一个难题:那就是要判断v在不在u的子树内。我们可以先以1为根,然后access(v),这样1到v的路径就跑到了一棵splay上;对点u进行splay操作,那么如果点u在1到v的路径上,它也会在那棵splay上。所以如果点u在那条路径上,splay完u后,点1就为该splay上最左(键值最小)的点了。暴力往下跳即可,复杂度即为一次splay的复杂度(经过的点数是一致的):均摊
。
然后,假设v在u的子树内,我们就cut掉v到原树上v的父亲(我们还要维护原树的形态,不过只须维护父亲)的边,然后将原树中v的父亲赋为u,并在LCT中Link一条v到u的边。
时间复杂度:
。
Code
#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
#define A son[x][0]
#define B son[x][1]
#define fo(i,a,b) for(i=a;i<=b;i++)
const int N=1e5+1;
int i,n,q,w[N],fa[N],son[N][2],fat[N],t,u,v;
bool tag[N];
void read(int&x)
{
char ch=getchar();x=0;
for(;!isdigit(ch);ch=getchar());
for(;isdigit(ch);x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
}
void push(int x)
{
if(!tag[x])return;
if(A)tag[A]=!tag[A],swap(son[A][0],son[A][1]);
if(B)tag[B]=!tag[B],swap(son[B][0],son[B][1]);
tag[x]=0;
}
inline bool so(int x){return son[fat[x]][1]==x;}
inline void link(int f,int x,bool d)
{
if(!x){son[f][d]=0;return;}
if(fat[x]=f)son[f][d]=x;
}
inline bool if_root(int x){return !fat[x]||son[fat[x]][so(x)]!=x;}
void rotate(int x)
{
if(!x)return;
int y=fat[x],z=fat[y],k=so(x),b=son[x][!k];
link(y,b,k);
if(!if_root(y))
link(z,x,so(y));
else fat[x]=z;
link(x,y,!k);
}
int d[N];
void clear(int x)
{
d[d[0]=1]=x;
while(!if_root(x))d[++d[0]]=x=fat[x];
while(d[0])push(d[d[0]--]);
}
void splay(int x)
{
clear(x);
for(int f=fat[x];!if_root(x);rotate(x),f=fat[x])
rotate(!if_root(f)?so(x)==so(f)?f:x:0);
}
void splay(int x,int y)
{
clear(x);
for(int f=fat[x];f!=y;rotate(x),f=fat[x])
rotate(fat[f]!=y?so(x)==so(f)?f:x:0);
}
void access(int y)
{
int x=0;
while(y)
{
splay(y);
link(y,x,1);
x=y;
y=fat[y];
}
}
void evert(int x)
{
tag[x]=!tag[x];
swap(A,B);
}
void makeroot(int x)
{
access(x);
splay(x);
evert(x);
}
//Link_Cut_Tree
void dfs(int x)
{
if(!x)return;
d[++d[0]]=w[x];
dfs(A);dfs(B);
}
bool check()
{
d[1]=w[u],d[d[0]=2]=w[v];
dfs(son[v][!so(v)]);
if(d[0]<3)return 0;
sort(d+1,d+d[0]+1);
int i;
fo(i,1,d[0]-2)if(d[i]+d[i+1]>d[i+2])return 1;
return 0;
}
int findlst(int x){while(A)x=A;return x;}
bool if_son(int x,int y)
{
makeroot(1);
access(y);
splay(x);
return findlst(x)==1;
}
void cut(int x,int y)
{
makeroot(x);
access(y);
splay(x);
splay(y,x);
son[x][so(y)]=fat[y]=0;
}
void Link(int x,int y)
{
makeroot(x);
fat[x]=y;
}
int main()
{
freopen("triangle.in","r",stdin);
freopen("triangle.out","w",stdout);
read(n);read(q);
fo(i,1,n)read(w[i]);
fo(i,1,n-1)read(fa[i+1]),fat[i+1]=fa[i+1];
fo(i,1,q)
{
read(t);read(u);read(v);
switch(t)
{
case 1:
{
if(u==v){printf("N\n");continue;}
makeroot(u);
access(v);
splay(u);
splay(v,u);
printf(check()?"Y\n":"N\n");
break;
}
case 2:w[u]=v;break;
case 3:
{
if(u==v)continue;
if(if_son(u,v))
cut(v,fa[v]),Link(v,fa[v]=u);
else cut(u,fa[u]),Link(u,fa[u]=v);
}
}
}
}
我把上面的程序交上去:
However,这只是我对未来美好生活的愿景。真实的情况是:
我为此修改了无数次,总以为是LCT打错了,调了将近一个小时,终于看到题目的限定条件有一行:每个节点的权值<=(2^31)-1;而我的程序有一行:fo(i,1,d[0]-2)if(d[i]+d[i+1]>d[i+2])return 1;
然后我没开ll,然后它就自然溢出了。。。o(╥﹏╥)o
不过这个mistake,c++也要负一半责任——谁叫它算数运算上溢了,不爆RE,而是放任它自然溢出呢!Pascal就很友好。
正确的代码如下:
#include <cstdio>
#include <cctype>
#include <algorithm>
using namespace std;
#define A son[x][0]
#define B son[x][1]
#define ll long long
#define fo(i,a,b) for(i=a;i<=b;i++)
const int N=1e5+1;
int i,n,q,w[N],fa[N],son[N][2],fat[N],t,u,v;
bool tag[N];
void read(int&x)
{
char ch=getchar();x=0;
for(;!isdigit(ch);ch=getchar());
for(;isdigit(ch);x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
}
void evert(int x)
{
tag[x]=!tag[x];
swap(A,B);
}
void push(int x)
{
if(!tag[x])return;
if(A)evert(A);
if(B)evert(B);
tag[x]=0;
}
inline bool so(int x){return son[fat[x]][1]==x;}
inline void link(int f,int x,bool d)
{
if(!x){son[f][d]=0;return;}
if(fat[x]=f)son[f][d]=x;
}
inline bool if_root(int x){return !fat[x]||son[fat[x]][so(x)]!=x;}
void rotate(int x)
{
if(!x)return;
int y=fat[x],z=fat[y],k=so(x),b=son[x][!k];
link(y,b,k);
if(!if_root(y))
link(z,x,so(y));
else fat[x]=z;
link(x,y,!k);
}
int d[N];
void clear(int x)
{
d[d[0]=1]=x;
while(!if_root(x))d[++d[0]]=x=fat[x];
while(d[0])push(d[d[0]--]);
}
void splay(int x)
{
clear(x);
for(int f=fat[x];!if_root(x);rotate(x),f=fat[x])
rotate(!if_root(f)?so(x)==so(f)?f:x:0);
}
void access(int y)
{
int x=0;
while(y)
{
splay(y);
link(y,x,1);
x=y;
y=fat[y];
}
}
void makeroot(int x)
{
access(x);
splay(x);
evert(x);
}
//Link_Cut_Tree
void dfs(int x)
{
if(!x)return;
d[++d[0]]=w[x];
if(d[0]>50)return;
dfs(A);
if(d[0]>50)return;
dfs(B);
}
bool query()
{
makeroot(u);
access(v);
splay(v);
d[0]=0;
dfs(v);
if(d[0]>50)return 1;
sort(d+1,d+d[0]+1);
int i;
fo(i,1,d[0]-2)if((ll)d[i]+(ll)d[i+1]>(ll)d[i+2])return 1;
return 0;
}
int findlst(int x){while(A)x=A;return x;}
bool if_son(int x,int y)
{
makeroot(1);
access(y);
splay(x);
return findlst(x)==1;
}
void cut(int x,int y)
{
makeroot(x);
access(y);
splay(y);
son[y][0]=fat[x]=0;
}
void Link(int x,int y)
{
makeroot(x);
fat[x]=y;
}
int main()
{
freopen("triangle.in","r",stdin);
freopen("triangle.out","w",stdout);
read(n);read(q);
fo(i,1,n)read(w[i]);
fo(i,1,n-1)read(fa[i+1]),fat[i+1]=fa[i+1];
fo(i,1,q)
{
read(t);read(u);read(v);
switch(t)
{
case 1:
{
if(u==v){printf("N\n");continue;}
printf(query()?"Y\n":"N\n");
break;
}
case 2:w[u]=v;break;
case 3:
{
if(u==v)continue;
if(if_son(u,v))
cut(v,fa[v]),Link(v,fa[v]=u);
else cut(u,fa[u]),Link(u,fa[u]=v);
break;
}
}
}
}