这个题目描述了一个有趣的星际传送门系统。想象你正在玩一个宇宙探索游戏,每个行星都有一个固定的传送门,可以将你传送到另一个行星(可能是自己)。现在你需要处理大量查询,每个查询给出起点和终点,要求计算出最少需要多少次传送才能到达目的地。
这类问题在图论中被称为"函数图"(Functional Graph)问题,因为每个节点(行星)恰好有一条出边(传送门)。这种结构具有以下数学特性:
实际应用场景包括:
首先我们需要识别图中的所有环和链结构。使用三色标记法进行深度优先搜索:
当从节点i开始遍历时:
为了高效处理"走k步"的查询,我们使用倍增法预处理每个节点走2^i步后的位置。构建一个二维数组dp,其中:
预处理过程:
cpp复制for(int j=1;j<20;j++) {
for(int i=1;i<=n;i++) {
dp[i][j] = dp[dp[i][j-1]][j-1];
}
}
这个预处理使得我们可以在O(logk)时间内计算任意k步的移动。
对于查询(a,b),我们需要考虑四种情况:
环检测是算法的核心部分。我们使用vector记录当前路径,当遇到状态1的节点时:
cpp复制if(s[cur]==1){ // 找到环
cycle_ans++; // 新的环编号
int st = find(path.begin(),path.end(),cur)-path.begin();
vector<int> temp(path.begin()+st,path.end());
len[cycle_ans] = temp.size();
// 标记环上节点
for(int j=0;j<temp.size();j++){
node[temp[j]] = j;
cycle[temp[j]] = cycle_ans;
s[temp[j]] = 2;
}
// 处理链上节点
for(int j=st-1;j>=0;j--){
cycle[path[j]] = cycle_ans;
d[path[j]] = st-j;
s[path[j]] = 2;
}
break;
}
查询函数需要处理所有四种情况:
cpp复制int query(int a, int b){
if(cycle[a]!=cycle[b]) return -1;
int nn = len[cycle[a]];
if(node[a]==-1 && node[b]==-1){ // 都在链上
int t = d[a]-d[b];
if(t<0 || fun(a,t)!=b) return -1;
return t;
}
if(node[a]==-1){ // a在链上,b在环上
int t = d[a];
int c = fun(a,t);
return (node[b]-node[c]+nn)%nn + t;
}
if(node[a]!=-1 && node[b]!=-1){ // 都在环上
return (node[b]-node[a]+nn)%nn;
}
return -1;
}
总复杂度:O(nlogn + q)
使用以下数组存储必要信息:
总空间复杂度:O(nlogn)
需要特别注意以下边界情况:
cpp复制ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
循环展开:对于固定次数的循环(如20次倍增)
内存局部性优化:合理安排数组访问顺序
如果允许动态修改传送门目标,可以考虑:
如果每次传送有不同的代价,可以:
如果每个节点有多个传送门选择,问题将转化为:
在实际编程竞赛中,这种问题通常出现在需要高效处理大量查询的场景。我建议在实现时先确保正确性,再考虑优化。可以先写一个暴力版本作为参考,再逐步引入倍增等优化技术。