[POI2012]RAN-Rendezvous
<body>
<center><h1>2791: [Poi2012]Rendezvous </h1><span class="green">Time Limit: </span>25 Sec <span class="green">Memory Limit: </span>128 MB<br><span class="green">Submit: </span>259 <span class="green">Solved: </span>160<br>[<a href="submitpage.php?id=2791">Submit</a>][<a href="problemstatus.php?id=2791">Status</a>][<a href="bbs.php?id=2791">Discuss</a>]</center><h2>Description</h2><div class="content"><p><span style="font-size: medium"><br>
给定一个n个顶点的有向图,每个顶点有且仅有一条出边。<br>
对于顶点i,记它的出边为(i, a[i])。<br>
再给出q组询问,每组询问由两个顶点a、b组成,要求输出满足下面条件的x、y:<br>
1. 从顶点a沿着出边走x步和从顶点b沿着出边走y步后到达的顶点相同。<br>
2. 在满足条件1的情况下max(x,y)最小。<br>
3. 在满足条件1和2的情况下min(x,y)最小。<br>
4. 在满足条件1、2和3的情况下x>=y。<br>
如果不存在满足条件1的x、y,输出-1 -1。</span></p>
<p></p></div><h2>Input</h2><div class="content"><p><span style="font-size: medium">第一行两个正整数n和q (n,q<=500,000)。<br>
第二行n个正整数a[1],a[2],...,a[n] (a[i]<=n)。<br>
下面q行,每行两个正整数a,b (a,b<=n),表示一组询问。</span></p>
<p></p></div><h2>Output</h2><div class="content"><p><span style="font-size: medium">输出q行,每行两个整数。</span></p>
<p></p></div><h2>Sample Input</h2>
<div class="content"><span class="sampledata">12 5<br>
4 3 5 5 1 1 12 12 9 9 7 1<br>
7 2<br>
8 11<br>
1 2<br>
9 10<br>
10 5<br>
<br>
</span></div><h2>Sample Output</h2>
<div class="content"><span class="sampledata">2 3<br>
1 2<br>
2 2<br>
0 1<br>
-1 -1<br>
<br>
</span></div><h2>HINT</h2>
<div class="content"><p></p></div><h2>Source</h2>
<div class="content"><p><a href="problemset.php?search=鸣谢Oimaster">鸣谢Oimaster</a></p></div><center>[<a href="submitpage.php?id=2791">Submit</a>][<a href="problemstatus.php?id=2791">Status</a>][<a href="bbs.php?id=2791">Discuss</a>]</center><br>
<a href="./"><span class="red">HOME</span></a>
<a href="javascript:history.go(-1)"><span class="red">Back</span></a>
</body>
</body>
题解
我是很久没有好好写一篇题解了。
首先分析题意,是要沿着出边跳到相同的节点,说明要求的是lca。
然后说一下基环树的套路:
- 树上倍增可以照常写,注意使用的条件就行了。
- 一遍拓扑排序把环找出来,这时候连通且未标记的肯定都在同一个环内。
- 以环上任意节点为起点开始做一个环标号,环长度前缀和,还有环总长。具体可以直接用出边跳。
- 把不在环上的点的出边反向用来建树,以环为根处理深度,环根标号。
这样询问时的处理套路:
- 不在同一连通块内,即环根标号对应节点的环标号不同,那么lca不存在。
- 在同一棵树内,即环根标号相同,直接树上lca解决。
- 这题我们主要解决的情况——在同一连通块内,但是不在同一棵树内,即环根标号对应节点的环标号相同,但环根标号不同。
x,y肯定要调到环上,然后分为两种情况,x,y分别跳。用预处理的3解决。
这样询问时间复杂度可以做到O(1)O(1),不过这题我的简便写法是O(logn)O(logn)的。总时间复杂度((n+m)logn)((n+m)logn)
#include<bits/stdc++.h> #define co const #define il inline #define rg register template<class T>T read(){ rg T data=0,w=1;rg char ch=getchar(); for(;!isdigit(ch);ch=getchar())if(ch=='-') w=-w; for(;isdigit(ch);ch=getchar()) data=data*10+ch-'0'; return data*w; } template<class T>T read(T&x){ return x=read<T>(); } typedef long long ll; using namespace std; typedef pair<int,int> pii; #define x first #define y second co int N=5e5+1; int n,m,t,f[N][20],deg[N]; int pos[N],cnt,len[N],s[N]; int d[N],id[N]; vector<int> e[N]; queue<int> q; int lca(int x,int y){ if(d[x]>d[y]) swap(x,y); for(int i=19;i>=0;--i) if(d[f[y][i]]>=d[x]) y=f[y][i]; if(x==y) return x; for(int i=19;i>=0;--i) if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i]; return f[x][0]; } pii cmp(co pii&a,co pii&b){ if(max(a.x,a.y)<max(b.x,b.y)) return a; if(max(a.x,a.y)>max(b.x,b.y)) return b; if(min(a.x,a.y)<min(b.x,b.y)) return a; if(min(a.x,a.y)>min(b.x,b.y)) return b; return a.x>=a.y?a:b; } int main(){ read(n),read(m); for(int i=1;i<=n;++i) ++deg[read(f[i][0])]; // prework t=log(n)/log(2); for(int i=1;i<=t;++i) for(int x=1;x<=n;++x) f[x][i]=f[f[x][i-1]][i-1]; // topsort for(int i=1;i<=n;++i)if(!deg[i]) q.push(i); while(q.size()){ int x=q.front();q.pop(); if(!--deg[f[x][0]]) q.push(f[x][0]); } // circle for(int i=1;i<=n;++i)if(deg[i]&&!pos[i]){ ++cnt; for(int j=i;!pos[j];j=f[j][0]) pos[j]=cnt,s[j]=++len[cnt]; } // bfs for(int i=1;i<=n;++i){ if(pos[i]) id[i]=i,q.push(i); else e[f[i][0]].push_back(i); } while(q.size()){ int x=q.front();q.pop(); for(unsigned i=0;i<e[x].size();++i){ int y=e[x][i]; d[y]=d[x]+1,id[y]=id[x]; q.push(y); } } while(m--){ int x=read<int>(),y=read<int>(); if(pos[id[x]]!=pos[id[y]]) puts("-1 -1"); else if(id[x]==id[y]){ int p=lca(x,y); printf("%d %d\n",d[x]-d[p],d[y]-d[p]); } else{ pii a,b; int sx=s[id[x]],sy=s[id[y]],now=len[pos[id[x]]]; a.x=d[x]+(sy-sx+now)%now,a.y=d[y]; b.x=d[x],b.y=d[y]+(sx-sy+now)%now; pii ans=cmp(a,b); printf("%d %d\n",ans.x,ans.y); } } return 0; }
静渊以有谋,疏通而知事。
标签:
基环树
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?