Loj#2474-「2018 集训队互测 Day 3」北校门外的未来【LCT】

1|0正题

题目链接:https://loj.ac/p/2474


1|1题目大意

开始有一个只有点1的图,一个点x能走到点y当且仅当路径(x,y)之间(不包括x,y)不存在编号比xy要大的节点。有m次操作:

  1. 新建一个编号为y的节点和x连接,保证编号不重复。
  2. 询问x走到y最少需要走多少次。

节点编号在1n之间

1n105,1m5×105


1|2解题思路

神仙题。

考虑构造一个类似笛卡尔树的东西,我们每次找到编号最大的点作为中心然后连接向它分割出来的连通块的中心。(相当于找编号最大的点的点分树)

我们记原树为T,这棵树为T,那么在T上一个点能跳到的点肯定都是它的祖先。考虑怎么样的祖先能够被他跳到,若x能跳到它的祖先y,有两种情况

  1. yxT上的父节点
  2. T的路径(x,y)上距离y最近的点为z,如果Tzx的子树内,那么x能跳到y。(这个不难证明,因为这样xy的路径上没有它的其他祖先)

并且还有一个性质,若x能跳到zy为他的父节点,那么y也能跳到z,因为显然y绕去x一圈回来都行。

那么根据这个性质从贪心的角度思考,若我们的询问为x,y距离较远,视为x,y同时跳到它的LCA,那在大部分时候我们x,y都往能到达的深度最浅的节点跳是优的。

事实上也是这样,我们考虑x,y跳到x,y满足它们再往上跳深度就小于或等于LCA了,记此时的跳跃次数为c,那么答案肯定是c+2或者c+3
因为若xy都能跳到LCA,次数就是c+2,否则就都往上跳一步,这样xy一点是祖孙关系(记x深度小),又因为原来的x能跳上来,那么这个新的y也肯定能跳到那个位置,此时答案为c+3

好那么现在我们就只需要处理祖孙的问题了,我们要支持对于x,yx跳到不超过y的最少步数和位置。这样询问时我们跳到x,y然后再查询xy能否到达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的后面,然后整条路径都变成虚边,xy的连接则变成实边。
会发现再到后面我们提路径(z,x)时会发现它在G被分割成若干段,我们将这些段拼接起来。我们再把连上的边在T上标记起来,会发现这个过程是一个类似于LCT的Access的过程,均摊下来是O(n)次操作。

因为有断边连接操作,所以上述的G我们需要用一个LCT来维护,同时还需要维护一个T以方便我们找到每一段的位置。
然后我们需要精准的找到每一个实边修改,我们可以维护一个实边的和,然后在Splay上二分位置暴力修改。

时间复杂度:O(nlog2n)


1|3code

#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; }

__EOF__

本文作者QuantAsk
本文链接https://www.cnblogs.com/QuantAsk/p/16249258.html
关于博主:退役OIer,GD划水选手
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   QuantAsk  阅读(79)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 张高兴的大模型开发实战:(一)使用 Selenium 进行网页爬虫
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示