分析:
从学姐blog中扒出来的一道神题
小范围数据的暴力非常的显然,然而MLE和TLE不是我们的重点
一开始就想到把需要插入的子树直接视为一个点
加上原树,我们得到的就是一个有m+1个结点的重构树
最初的模板树缩成一点,作为大树的根结点
我们考虑插入一个子树
子树加入时会重新标号,但是大小顺序是不变的
所以一个结点代表的子树的结点编号一定是一段连续的区间
我们可以通过二分的方式查找到这个点所属的缩点后的结点,然后连接
这个时候需要注意,此时连接的边是有边权的
边权为加入的子树的根到父结点根的距离
(父结点实际上也是一个子树缩成的一个点)
在上述的插入过程中
我们需要记录连接的准确节点
但是暴力的时间复杂度是不科学的
然而子树中的结点编号大小有序,所以我们只要知道了这棵子树最小的编号,就能确定连接结点在子树中是第几大
对模板树求出dfs序列,这样子树都是连续的区间
求解区间第k大,可以用静态主席树完成
我们这样就得到了一棵m+1个结点的重构树
下面的问题就是路径长度的查询
首先想到一定是先确定两个结点(x,y)分别位于重构树中的哪个结点中
以及这两个结点在重构树中相距的距离
之后我们再在该节点的内部做LCA
口hu的题解
代码几乎不可写,于是只能%学姐的代码
网上都是dalao的题解(dalao眼里这题不是很难,所以题解对于代码实现不是很详细)
只能自己看代码,慢慢理解:
首先
我们肯定要建出原树
我们dfs一遍原树,处理出我们需要的信息:
d
f
n
dfn
dfn:dfs序
c
l
o
clo
clo:时间戳
p
r
e
pre
pre_1:原树中的倍增父结点
d
i
s
dis
dis_1:原树中的倍增路径长度
d
e
e
p
deep
deep_1:原树中的结点深度
s
i
z
e
size
size:原树中的子树大小
i
n
in
in:子树在dfs序中的起始位置
o
u
t
out
out:子树在dfs序中的终止位置
void dfs_1(int now,int faa)
{
for (int i=1;i<=20;i++)
{
if (deep_1[now]-(1<<i)<0) break;
pre_1[now][i]=pre_1[pre_1[now][i-1]][i-1];
dis_1[now][i]=dis_1[now][i-1]+dis_1[pre_1[now][i-1]][i-1];
}
fa[now]=faa; dfn[++clo]=now; in[now]=clo; size[now]=1;
for (int i=st_1[now];i;i=way_1[i].nxt)
if (way_1[i].y!=faa)
{
deep_1[way_1[i].y]=deep_1[now]+1;
pre_1[way_1[i].y][0]=now; dis_1[way_1[i].y][0]=1;
dfs_1(way_1[i].y,now);
size[now]+=size[way_1[i].y];
}
out[now]=clo;
}
这一部分还是比较简单,容易理解的
之后我们就要按照dfs序建立主席树:
void insert(int &now,int l,int r,int x)
{
top++;
t[top]=t[now];
now=top; t[now].sum++;
if (l==r) return;
int mid=(l+r)>>1;
if (x<=mid) insert(t[now].l,l,mid,x);
else insert(t[now].r,mid+1,r,x);
}
int ask(int x,int y,int l,int r,int k)
{
if (l==r) return r;
int tmp=t[t[y].l].sum-t[t[x].l].sum;
int mid=(l+r)>>1;
if (tmp>=k) return ask(t[x].l,t[y].l,l,mid,k);
else return ask(t[x].r,t[y].r,mid+1,r,k-tmp);
}
建立主席树和第k大查询都是比较基础的
这里我们需要进一步说明一下为什么要用主席树查询第k大:
我们的建树过程实际上是对原树的一个局部复制,所以所有操作都可以放到原树上完成
考虑插入了一棵子树
而子树的结点编号和原树中的结点编号的大小关系是一样的
如果我们知道一点在新的子树中排第x
就可以在原树中查找这棵子树中的第x大,找到这个点在原树上的位置
相应的操作就可以在原树上完成了
缩点
我们开始建树
f
f
f:结点i的子树中结点编号的最大值
L
L
L:子树在dfs序中的起始位置
R
R
R:子树在dfs序中的结束位置
b
e
l
o
n
g
belong
belong:缩成的结点中根的编号
p
r
e
pre
pre:子树的父结点位于父子树中的第几大
//缩点并插入
f[0]=0; f[1]=size[1]; L[1]=1; R[1]=size[1]; belong[1]=1;
o=1; //新结点计数器
for (int i=1;i<=m;i++)
{
int a; ll b;
scanf("%d%lld",&a,&b);
int t=find(b); //找到插入的准确结点
ll now=b-f[t-1]; //在小树DFS序中的排名
int rak=ask(root[L[t]-1],root[R[t]],1,n,now);
o++;
f[o]=f[o-1]+size[a]; //这个节点中的编号范围
L[o]=in[a]; R[o]=out[a]; belong[o]=a; pre[o]=rak;
build(o,t,deep_1[rak]-deep_1[belong[t]]+1);
}
可以注意到,在这里有一个find函数
这个函数的作用就是定位结点在哪一个缩点内的
注意到f数组实际上是一个类似前缀和的定义
所以我们可以用二分确定:
int find(ll x) //定位在哪一个块内
{
int l=1,r=o;
while (l<=r)
{
int mid=(l+r)>>1;
if (x<=f[mid-1]) r=mid-1;
else if (x>f[mid]) l=mid+1;
else return mid;
}
}
我们通过build函数建立了重构树,下面就需要遍历一下重构树,维护相应信息:
d
e
e
p
deep
deep_2:重构树中缩点的深度
p
r
e
pre
pre_2:重构树中缩点的倍增父结点
d
i
s
dis
dis_2:重构树中缩点的倍增路径长度
void dfs_2(int now,int faa)
{
for (int i=1;i<=20;i++)
{
if (deep_2[now]-(1<<i)<0) break;
pre_2[now][i]=pre_2[pre_2[now][i-1]][i-1];
dis_2[now][i]=dis_2[now][i-1]+dis_2[pre_2[now][i-1]][i-1];
}
for (int i=st[now];i;i=way[i].nxt)
if (way[i].y!=faa)
{
deep_2[way[i].y]=deep_2[now]+1;
pre_2[way[i].y][0]=now;
dis_2[way[i].y][0]=way[i].v;
dfs_2(way[i].y,now);
}
}
目前为止,我们得到了原树和重构树的所有信息
就可以倍增的计算lca了
在代码中
l
c
a
lca
lca函数:原树的lca
L
C
A
LCA
LCA函数:重构树的lca
有点不一样的就是LCA函数:
void cl(int k,int x)
{
for (int i=0;i<k;i++)
x=pre_2[x][i];
nowy=x;
}
ll LCA(int x,int y)
{
if (deep_2[x]<deep_2[y]) swap(x,y);
ll d=deep_2[x]-deep_2[y];
ll ans=0;
for (int i=0;i<=20;i++)
if (d==(1<<i))
{
cl(i,x);
break;
}
int t=d;
for (int i=20;i>=0;i--) if ((d>>i)&1)
{
t-=(1<<i);
ans+=dis_2[x][i];
x=pre_2[x][i];
for (int j=0;j<=20;j++)
if (t==(1<<j))
{
cl(j,x);
break;
}
}
if (x==y)
{
flag=0; nowx=x;
return ans;
}
for (int i=20;i>=0;i--)
if (pre_2[x][i]!=pre_2[y][i])
{
ans+=dis_2[x][i];
ans+=dis_2[y][i];
x=pre_2[x][i]; y=pre_2[y][i];
}
nowx=x; nowy=y;
return ans+2;
}
这里就有比较多的细节了
如果
x
,
y
x,y
x,y的
L
C
A
LCA
LCA不是
x
x
x或
y
y
y,那么我们在
L
C
A
LCA
LCA的最后一步不让他们向上跳就可以得到与LCA相连的节点
所以我们不跳最后一步也不计入答案,但是
a
n
s
ans
ans要+2
因为我们这两个缩点连接到LCA上需要一个短边
找到这两个结点连接的位置,回原树做lca即可
对于其中一个点是
L
C
A
LCA
LCA的情况
我们需要在蹦最后一个
2
k
2^k
2k步的时候,单独处理一下(即在外过程跳
2
k
−
1
2^k-1
2k−1步,$2k=20+21+…+2(k-1) $)
同时我们需要维护几个值:
n
o
w
x
nowx
nowx:x当前在的缩点
n
o
w
y
nowy
nowy:y当前所在的缩点
最后
就是利用以上所有函数回答问题了
如果x和y就在一个缩点内,我们直接做原树lca即可
稍微复杂一点的就是两个点不在同一缩点内
而这又需要分成两种情况:
lca(x,y)=x或y
lca(x,y)=新结点
tip
开ll
因为到后期新结点的编号回高达
n
2
n^2
n2
#include<cstdio>
#include<cstring>
#include<iostream>
#define ll long long
using namespace std;
const ll N=100005;
const ll lg=20;
struct node{
int y,nxt;
ll v;
};
node way_1[N<<1],way[N<<1];
int n,m,Q,st_1[N],st[N],tot=0,tt=0,top=0;
int dfn[N],clo=0,in[N],out[N],pre[N];
int root[N],belong[N],o,lcanow,nowy,nowx;
ll deep_1[N],size[N],pre_1[N][21],dis_1[N][21],fa[N];
ll L[N],R[N],f[N];
ll deep_2[N],pre_2[N][21],dis_2[N][21];
bool flag;
struct Tree{
int sum,l,r;
};
Tree t[N*20];
void add_t(int u,int w) //建立原树
{
tt++; way_1[tt].y=w;way_1[tt].nxt=st_1[u];st_1[u]=tt;
tt++; way_1[tt].y=u;way_1[tt].nxt=st_1[w];st_1[w]=tt;
}
void build(int u,int w,ll z)
{
tot++; way[tot].y=w;way[tot].v=z;way[tot].nxt=st[u];st[u]=tot;
tot++; way[tot].y=u;way[tot].v=z;way[tot].nxt=st[w];st[w]=tot;
}
void dfs_1(int now,int faa)
{
for (int i=1;i<=20;i++)
{
if (deep_1[now]-(1<<i)<0) break;
pre_1[now][i]=pre_1[pre_1[now][i-1]][i-1];
dis_1[now][i]=dis_1[now][i-1]+dis_1[pre_1[now][i-1]][i-1];
}
fa[now]=faa; dfn[++clo]=now; in[now]=clo; size[now]=1;
for (int i=st_1[now];i;i=way_1[i].nxt)
if (way_1[i].y!=faa)
{
deep_1[way_1[i].y]=deep_1[now]+1;
pre_1[way_1[i].y][0]=now; dis_1[way_1[i].y][0]=1;
dfs_1(way_1[i].y,now);
size[now]+=size[way_1[i].y];
}
out[now]=clo;
}
void insert(int &now,int l,int r,int x)
{
top++;
t[top]=t[now];
now=top; t[now].sum++;
if (l==r) return;
int mid=(l+r)>>1;
if (x<=mid) insert(t[now].l,l,mid,x);
else insert(t[now].r,mid+1,r,x);
}
int ask(int x,int y,int l,int r,int k)
{
if (l==r) return r;
int tmp=t[t[y].l].sum-t[t[x].l].sum;
int mid=(l+r)>>1;
if (tmp>=k) return ask(t[x].l,t[y].l,l,mid,k);
else return ask(t[x].r,t[y].r,mid+1,r,k-tmp);
}
int find(ll x) //定位在哪一个块内
{
int l=1,r=o;
while (l<=r)
{
int mid=(l+r)>>1;
if (x<=f[mid-1]) r=mid-1;
else if (x>f[mid]) l=mid+1;
else return mid;
}
}
void dfs_2(int now,int faa)
{
for (int i=1;i<=20;i++)
{
if (deep_2[now]-(1<<i)<0) break;
pre_2[now][i]=pre_2[pre_2[now][i-1]][i-1];
dis_2[now][i]=dis_2[now][i-1]+dis_2[pre_2[now][i-1]][i-1];
}
for (int i=st[now];i;i=way[i].nxt)
if (way[i].y!=faa)
{
deep_2[way[i].y]=deep_2[now]+1;
pre_2[way[i].y][0]=now;
dis_2[way[i].y][0]=way[i].v;
dfs_2(way[i].y,now);
}
}
ll lca(int x,int y)
{
if (deep_1[x]<deep_1[y]) swap(x,y);
ll d=deep_1[x]-deep_1[y];
ll ans=0;
if (d)
for (int i=0;i<=20&&d;i++,d>>=1)
if (d&1)
ans+=dis_1[x][i],x=pre_1[x][i];
lcanow=y;
if (x==y) return ans;
for (int i=20;i>=0;i--)
if (pre_1[x][i]!=pre_1[y][i])
{
ans+=dis_1[x][i];
ans+=dis_1[y][i];
x=pre_1[x][i]; y=pre_1[y][i];
}
lcanow=pre_1[x][0];
return ans+2;
}
void cl(int k,int x)
{
for (int i=0;i<k;i++)
x=pre_2[x][i];
nowy=x;
}
ll LCA(int x,int y)
{
if (deep_2[x]<deep_2[y]) swap(x,y);
ll d=deep_2[x]-deep_2[y];
ll ans=0;
for (int i=0;i<=20;i++)
if (d==(1<<i))
{
cl(i,x);
break;
}
int t=d;
for (int i=20;i>=0;i--) if ((d>>i)&1)
{
t-=(1<<i);
ans+=dis_2[x][i];
x=pre_2[x][i];
for (int j=0;j<=20;j++)
if (t==(1<<j))
{
cl(j,x);
break;
}
}
if (x==y)
{
flag=0; nowx=x;
return ans;
}
for (int i=20;i>=0;i--)
if (pre_2[x][i]!=pre_2[y][i])
{
ans+=dis_2[x][i];
ans+=dis_2[y][i];
x=pre_2[x][i]; y=pre_2[y][i];
}
nowx=x; nowy=y;
return ans+2;
}
int main()
{
scanf("%d%d%d",&n,&m,&Q);
for (int i=1;i<n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
add_t(x,y);
}
deep_1[1]=1;
dfs_1(1,0);
root[0]=0;
for (ll i=1;i<=n;i++)
{
root[i]=root[i-1];
insert(root[i],1,n,dfn[i]);
}
//缩点并插入
f[0]=0; f[1]=size[1]; L[1]=1; R[1]=size[1]; belong[1]=1;
o=1; //新结点计数器
for (int i=1;i<=m;i++)
{
int a; ll b;
scanf("%d%lld",&a,&b);
int t=find(b); //找到插入的准确结点
ll now=b-f[t-1]; //在小树DFS序中的排名
int rak=ask(root[L[t]-1],root[R[t]],1,n,now);
o++;
f[o]=f[o-1]+size[a]; //这个节点中的编号范围
L[o]=in[a]; R[o]=out[a]; belong[o]=a; pre[o]=rak;
build(o,t,deep_1[rak]-deep_1[belong[t]]+1);
}
deep_2[1]=1;
dfs_2(1,0);
for (int i=1;i<=Q;i++)
{
ll x,y;
scanf("%lld%lld",&x,&y);
int xx=find(x); //在重构树中定位
int yy=find(y);
if (xx==yy)
{
int t=ask(root[L[xx]-1],root[R[xx]],1,n,x-f[xx-1]);
int tt=ask(root[L[yy]-1],root[R[yy]],1,n,y-f[yy-1]);
printf("%lld\n",lca(t,tt));
continue;
}
flag=1;
ll ans=LCA(xx,yy);
if (!flag)
{
if (deep_2[xx]<deep_2[yy]) swap(xx,yy),swap(x,y);
int t=ask(root[L[xx]-1],root[R[xx]],1,n,x-f[xx-1]);
ans+=deep_1[t]-deep_1[belong[xx]];
int rak=ask(root[L[nowx]-1],root[R[nowx]],1,n,y-f[nowx-1]);
ll k=lca(rak,pre[nowy]);
if (lcanow==rak) printf("%lld\n",ans-(deep_1[rak]-deep_1[belong[nowx]]));
else printf("%lld\n",ans-(deep_1[pre[nowy]]-deep_1[belong[nowx]])+k);
}
else
{
int t=ask(root[L[xx]-1],root[R[xx]],1,n,x-f[xx-1]);
int tt=ask(root[L[yy]-1],root[R[yy]],1,n,y-f[yy-1]);
ans+=deep_1[t]-deep_1[belong[xx]];
ans+=deep_1[tt]-deep_1[belong[yy]];
printf("%lld\n",ans+lca(pre[nowx],pre[nowy]));
}
}
return 0;
}