AcWing 3775. 数组补全(环图)
每日一题是不可能做出来的
参考:https://www.acwing.com/video/3319/
题目
给定一个 1∼n 的排列 f1,f2,…,fn。
已知,对于 1≤i≤n,fi≠i 始终成立。
现在,因为一些原因,数组中的部分元素丢失了。
请你将数组丢失的部分补全,要求数组在补全后仍然是一个 1∼n 的排列,并且对于 1≤i≤n, fi≠i 均成立。
输入输出
输入:
第一行包含整数 T,表示共有 T 组测试数据。
每组数据第一行包含一个整数 n。
第二行包含 n 个整数 f1,f2,…,fn。如果 fi=0,则表示 fi 已经丢失,需要补全。
输出:
每组数据一行,输出补全后的 f 数组,整数之间空格隔开。
如果方案不唯一,则输出任意合理方案即可。
思路
i只出现一次,fi也只出现一次,所以一定能构成环。(离散数学里叫圈)
将所有不缺失的元素构成环(可能有多个),i->fi构成一条有向边,不存在自环。
将缺失的元素填入任意一个环的缺口中,使环封闭,将其他的环也封闭。如果不存在有缺口的环,就将缺失的元素自己连成一个环。
代码实现
从头到尾遍历每个点,找到每个点所在环的头结点和尾结点,将缺失的元素加到头结点和尾结点之间(该操作只需要进行一次),将其他环头结点和尾结点相连。
需要三个数组出边p[N],入边q[N],st[N]记录当前点是否被访问过
根据思路自己写一遍
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200010;
int p[N],q[N];
bool st[N];
int main()
{
int T;
cin >> T;
while (T -- ){
memset(q, 0, sizeof q);
memset(st, 0, sizeof st);
int n;
cin >> n;
for (int i = 1; i <= n; i ++ ){
cin >> p[i]; //输入i的出边
q[p[i]] = i; //记录p[i]的入边
}
bool flag = false; //记录是否处理过缺失的点
for (int i = 1; i <= n; i ++ ){ //遍历每个点
if(st[i] || !p[i]) continue; //如果已经被访问过或者该点是缺失的点,就不做操作
st[i] = true;
int x = i, y = i; //寻找i所在环的头结点和尾结点
while(!st[p[x]] && p[x]){ //寻找i的头结点
x = p[x];
st[x] = true; //访问当前点
}
while(!st[q[y]] && q[y]){ //寻找x的尾结点
y = q[y];
st[y] = true;
}
if(p[x] == y) continue; //当前i所在的环没有缺口了
if(!flag){ //缺失的点还没有被操作过
flag = true; //更新flag标记
for (int j = 1; j <= n; j ++ ){ //寻找孤立点
if(!p[j] && !q[j]){
st[j] = true; //不要忘了更新这个点的状态
p[x] = j;
x = j;
}
}
}
p[x] = y; //头结点和尾结点相连
}
if(!flag){ //跳出循环孤立点还是没有被处理,说明所有环都已经封闭,需要将所有孤立点连成一个环
int x = 0, y = 0;
for (int i = 1; i <= n; i ++ ){ //寻找孤立点
if(!p[i]){
if(!x && !y) x = y = i; //找到第一个孤立点
else{
p[x] = i;
x = i;
}
}
}
p[x] = y;
}
for (int i = 1; i <= n; i ++ ){ //输出方案
cout << p[i] << " ";
}
cout << endl;
}
return 0;
}