Loj#2474-「2018 集训队互测 Day 3」北校门外的未来【LCT】
正题
题目大意
开始有一个只有点\(1\)的图,一个点\(x\)能走到点\(y\)当且仅当路径\((x,y)\)之间(不包括\(x,y\))不存在编号比\(x\)或\(y\)要大的节点。有\(m\)次操作:
- 新建一个编号为\(y\)的节点和\(x\)连接,保证编号不重复。
- 询问\(x\)走到\(y\)最少需要走多少次。
节点编号在\(1\sim n\)之间
\(1\leq n\leq 10^5,1\leq m\leq 5\times 10^5\)
解题思路
神仙题。
考虑构造一个类似笛卡尔树的东西,我们每次找到编号最大的点作为中心然后连接向它分割出来的连通块的中心。(相当于找编号最大的点的点分树)
我们记原树为\(T\),这棵树为\(T'\),那么在\(T'\)上一个点能跳到的点肯定都是它的祖先。考虑怎么样的祖先能够被他跳到,若\(x\)能跳到它的祖先\(y\),有两种情况
- \(y\)是\(x\)在\(T'\)上的父节点
- 记\(T\)的路径\((x,y)\)上距离\(y\)最近的点为\(z\),如果\(T'\)中\(z\)在\(x\)的子树内,那么\(x\)能跳到\(y\)。(这个不难证明,因为这样\(x\sim y\)的路径上没有它的其他祖先)
并且还有一个性质,若\(x\)能跳到\(z\),\(y\)为他的父节点,那么\(y\)也能跳到\(z\),因为显然\(y\)绕去\(x\)一圈回来都行。
那么根据这个性质从贪心的角度思考,若我们的询问为\(x,y\)距离较远,视为\(x,y\)同时跳到它的\(LCA\),那在大部分时候我们\(x,y\)都往能到达的深度最浅的节点跳是优的。
事实上也是这样,我们考虑\(x,y\)跳到\(x',y'\)满足它们再往上跳深度就小于或等于\(LCA\)了,记此时的跳跃次数为\(c\),那么答案肯定是\(c+2\)或者\(c+3\)。
因为若\(x'\)和\(y'\)都能跳到\(LCA\),次数就是\(c+2\),否则就都往上跳一步,这样\(x'\)和\(y'\)一点是祖孙关系(记\(x'\)深度小),又因为原来的\(x'\)能跳上来,那么这个新的\(y'\)也肯定能跳到那个位置,此时答案为\(c+3\)。
好那么现在我们就只需要处理祖孙的问题了,我们要支持对于\(x,y\)求\(x\)跳到不超过\(y\)的最少步数和位置。这样询问时我们跳到\(x',y'\)然后再查询\(x'\)和\(y'\)能否到达\(LCA\)就好了。
记\(G\)表示一棵树,对于点\(x\)它的父节点就是它在\(T'\)上能过跳到的深度最小的节点。
那么先考虑一次加点对\(T'\)的影响,如果\(x>y\),那么\(y\)直接接在\(x\)的后面就好了,\(G\)上也是同理。如果\(x<y\),那么则需要向上找到一个\(x\)的深度最浅的祖先\(z\)满足\(z<y\)然后将\(y\)插在它的上面。
先考虑此时\(y\)能够到达的点和原来\(z\)能够到达的点是一样的,直接在\(G\)上接替\(z\)的位置,\(z\)则先暂时接在\(y\)的后面。
然后需要注意的是此时\(x\)节点就是路径\((z,y)\)上距离\(y\)最近的点,可以考虑利用这个性质来维护\(G\)。那此时能到达\(y\)节点的除了\(z\)以外,还有在\(T'\)上路径\((x,y)\)的节点能直接到达\(y\)。
并且这些节点在\(G\)上会接在\(y\)的后面的当且仅当他们原来接的点比\(y\)要大。
这样说下来\(G\)似乎很难维护,因为既和\(T'\)的位置有关有和\(G\)的位置有关。考虑维护另一张图\(G'\),在这张图上我们将边分为虚边实边,实边是在\(G\)上实际存在的边,虚边则是代表这条边连接的两个节点在\(G\)上拥有同一个父亲。
开始时我们尽量让它保持\(T'\)的模样,对于一个\((x,y,z)\)(与上文的意思相同),那么我们提出路径\((z,x)\)将其接到\(y\)的后面,然后整条路径都变成虚边,\(x\)和\(y\)的连接则变成实边。
会发现再到后面我们提路径\((z,x)\)时会发现它在\(G'\)被分割成若干段,我们将这些段拼接起来。我们再把连上的边在\(T'\)上标记起来,会发现这个过程是一个类似于\(LCT\)的Access的过程,均摊下来是\(O(n)\)次操作。
因为有断边连接操作,所以上述的\(G'\)我们需要用一个\(LCT\)来维护,同时还需要维护一个\(T'\)以方便我们找到每一段的位置。
然后我们需要精准的找到每一个实边修改,我们可以维护一个实边的和,然后在\(Splay\)上二分位置暴力修改。
时间复杂度:\(O(n\log^2 n)\)
code
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<stack>
#define mp(x,y) make_pair(x,y)
using namespace std;
const int N=1e5+10;
struct node{
int to,next;
}a[N<<1];
int n,m,tot,op[N*5],rx[N*5],ry[N*5];
int cnt,pos[N],ls[N],fa[N],down[N],low[N];
int siz[N],dep[N],son[N],top[N],rfn[N],ed[N];
stack<int> stk;vector<int> G[N];
void addl(int x,int y){
a[++tot].to=y;
a[tot].next=ls[x];
ls[x]=tot;return;
}
void dfs1(int x){
while(!stk.empty()&&pos[stk.top()]>pos[x])
down[stk.top()]=x,stk.pop();
//考虑若y>x那么只有往y那部分跑的pos会小于pos[y]
stk.push(x);siz[x]=1;
for(int i=0;i<G[x].size();i++){
int y=G[x][i];
fa[y]=x;dep[y]=dep[x]+1;
dfs1(y);siz[x]+=siz[y];
if(siz[y]>siz[son[x]])son[x]=y;
}
return;
}
void dfs2(int x){
rfn[x]=++cnt;
if(son[x]){top[son[x]]=top[x];dfs2(son[x]);}
for(int i=0;i<G[x].size();i++){
int y=G[x][i];
if(y==son[x])continue;
top[y]=y;dfs2(y);
}
ed[x]=cnt;
return;
}
int LCA(int x,int y){
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])swap(x,y);
x=fa[top[x]];
}
return (dep[x]<dep[y])?x:y;
}
int gtop(int x,int y){
while(top[x]!=top[y]){
if(fa[top[x]]==y)return top[x];
x=fa[top[x]];
}
return son[y];
}
bool gfir(int x,int y){
int z=gtop(x,y);
return (rfn[x]<=rfn[low[z]]&&rfn[low[z]]<=ed[x]);
}
struct LCT{
int t[N][2],fa[N],cfa[N],w[N],c[N],son[N];
bool Nroot(int x)
{return fa[x]&&(t[fa[x]][0]==x||t[fa[x]][1]==x);}
bool Direct(int x)
{return t[fa[x]][1]==x;}
void PushUp(int x)
{w[x]=w[t[x][0]]+w[t[x][1]]+c[x];return;}
void Rotate(int x){
int y=fa[x],z=fa[y];
int xs=Direct(x),ys=Direct(y);
int w=t[x][xs^1];
if(Nroot(y))t[z][ys]=x;
t[y][xs]=w;t[x][xs^1]=y;
if(w)fa[w]=y;fa[y]=x;fa[x]=z;
PushUp(y);PushUp(x);return;
}
void Splay(int x){
while(Nroot(x)){
int y=fa[x];
if(!Nroot(y))Rotate(x);
else if(Direct(x)==Direct(y))
Rotate(y),Rotate(x);
else Rotate(x),Rotate(x);
}
return;
}
void Access(int x){
for(int y=0;x;y=x,x=fa[x])
Splay(x),t[x][1]=y,PushUp(x);
return;
}
int MakeTop(int x){
Splay(x);
if(!t[x][0])return fa[x];
x=t[x][0];
while(t[x][1])x=t[x][1];
Splay(x);t[x][1]=0;PushUp(x);
return x;
}
void Mdf(int x,int w)
{Splay(x);c[x]=w;PushUp(x);return;}
int GetTop(int x)
{Splay(x);while(t[x][0])x=t[x][0];return x;}
int GetBot(int x)
{Splay(x);while(t[x][1])x=t[x][1];return x;}
void Ins(int x,int y){
if(x>y){fa[y]=cfa[y]=x;c[y]=w[y]=1;return;}
int u,v;u=MakeTop(v=down[y]);
Mdf(y,c[v]);Mdf(v,!u);
fa[y]=u;fa[v]=y;
if(son[u]==v)son[u]=y;
if(u)son[y]=v;
int z=0,now=0,low=x;
while(x){
Splay(x);t[x][1]=z;PushUp(x);
if(w[x]){
while(x){
if(w[t[x][1]])x=t[x][1];
else if(c[x])break;
else x=t[x][0];
}
if(x>y)break;
u=MakeTop(x);
if(u>y)break;
Splay(low);t[low][1]=0;
if(son[low]){
Splay(son[low]);
fa[son[low]]=u;
Mdf(son[low],1);
son[low]=0;
}
int pre=cfa[x];Mdf(x,0);
x=GetBot(x);Splay(x);
if(now)fa[now]=x,son[x]=t[x][1]=GetTop(now);
now=x;z=0;
low=x=pre;
}
else z=x,x=fa[x];
}
cfa[y]=cfa[down[y]];cfa[down[y]]=y;
if(now)now=GetTop(now),Mdf(now,1),fa[now]=y;
return;
}
pair<int,int> Ask(int x,int y){
if(x==y)return mp(0,0);
Access(x);Splay(x);
int dis=0,top=0,pre=x;
while(x)
if(x<y)top=x,x=t[x][0];
else x=t[x][1];
Splay(top);x=t[top][1];
if(!w[x])return mp(0,pre);
dis=w[x];
while(x){
if(w[t[x][0]])x=t[x][0];
else if(c[x]){
if(t[x][0]){
x=t[x][0];
while(t[x][1])x=t[x][1];
return mp(dis,x);
}
return mp(dis,top);
}
else top=x,x=t[x][1];
}
return mp(dis,top);
}
int ct=0;
int Query(int x,int y){
ct++;
if(ct==10)
ct++,ct--;
if(x==y)return 0;
if(x>y)swap(x,y);
int lca=LCA(x,y);
if(lca==y){
pair<int,int> A=Ask(x,y);
int ans=A.first+2;
if(gfir(A.second,y))ans--;
return ans;
}
pair<int,int> A=Ask(x,lca);
pair<int,int> B=Ask(y,lca);
int ans=A.first+B.first+3;
if(gfir(A.second,lca)&&gfir(B.second,lca))ans--;
return ans;
}
}T;
int find(int x)
{return (fa[x]==x)?(x):(fa[x]=find(fa[x]));}
int main()
{
scanf("%d%d",&n,&m);n=1;
for(int i=1;i<=m;i++){
scanf("%d%d%d",&op[i],&rx[i],&ry[i]);n=max(n,ry[i]);
if(op[i]==1)addl(rx[i],ry[i]),addl(ry[i],rx[i]),pos[ry[i]]=i;
}
for(int i=1;i<=n;i++)fa[i]=i;
for(int x=1;x<=n;x++)
for(int i=ls[x];i;i=a[i].next){
int y=a[i].to;
if(find(y)<x)
G[x].push_back(find(y)),fa[find(y)]=x;
}
fa[n]=0;dep[n]=1;
dfs1(n);top[n]=n;dfs2(n);
for(int x=1;x<=n;x++)
for(int i=ls[x];i;i=a[i].next){
int y=a[i].to;
if(x>y)low[gtop(y,x)]=y;
}
//low[x]表示点x子树内离它父节点最近的点
for(int i=1;i<=m;i++){
if(op[i]==1)T.Ins(rx[i],ry[i]);
else printf("%d\n",T.Query(rx[i],ry[i]));
}
return 0;
}