数组补全
AcWing
来源: 3775.数组补全
这里难度给出是困难,但其实并不难,可能写的稍微麻烦一点,读懂的话很就很简单了
题意大概就是给出n个数,如果这个数为0,那么表示缺失,需要我们补一个数,最后将补好的数组输出
限制就是,这n个数是1-n中的数,并且第i个数不能为i
我这里的做法是转换成环图,将题意转换一下,其实就是构建环图,但是不可以有自环,就是自己指向自己的环,如果还是不太明白,那么看下面这张图,对于第一个样例
可以看出有一个缺口,第2个数没有连向下一个,第一个数也没有上一个数,第三个数既没有入也没有出,我们把这些点叫做流浪的点,可以想到,我们为了形成一个环,还要把所有的点用上,那就把3放到中间就可以了,2指向3,3指向1就构成了环
思路
遍历数组,对于每个点,找到头和尾,如果头和尾相连,那么直接跳过,如果没有,我们再找出所有流浪的点,加入到缺口中。对于后面所有还有缺口的环,直接头尾相连
注意点
1、st数组标记是否使用,因为每个点我们最多只需要动一次就行了
2、q数组表示上一个点,p数组表示小一点
3、如果没有缺口,那么流浪的点就要相互之间形成一个环
代码里有详细注释
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5 + 10;
int q[N], p[N];//下一个和上一个
int n, T;
bool st[N];
int main(){
cin >> T;
while(T --){
memset(st, 0, sizeof st);//初始化标记
memset(q, 0, sizeof q);//初始化上一个点
cin >> n;
for(int i = 1;i <= n;i++){
cin >> p[i];//如果第i个数的下一个点
q[p[i]] = i;//那么他的上一个点就是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;//x表示尾,y表示头
while(st[p[x]] == false && p[x]){//x往下找
x = p[x];
st[x] = true;//找到记得标记
}
while(st[q[y]] == false && q[y]){//y往上找
y = q[y];
st[y] = true;
}
if(p[x] == y) continue;//如果没有缺口直接跳过
if(!flag){//如果有缺口并且我们还没有处理流浪的点
flag = true;//表示处理过了,以后就不用处理了
for(int j = 1;j <= n;j++){
if(!q[j] && !p[j]){//找到所以没有入也没有出的,那就是流浪的点
p[x] = j;//接到x的后面
x = j;
st[j] = true;//标记
}
}
}
p[x] = y;//最后首尾相连封住缺口
}
if(!flag){
//如果我们遍历了一遍,还没有把流浪的点处理,也就是说,没有存在缺口,都是完美的环
//那么我们让流浪的点相互之间构成环
int x = 0, y = 0;//初始化头和尾是0
for(int i = 1;i <= n;i++){
if(!p[i]){
if(!x && !y) x = i, y = i;//找到第一个流浪的点作为头和尾
else{
p[x] = i;//从第二个开始往下找
x = i;
}
}
}
p[x] = y;//最后封个口
}
for(int i = 1;i <= n;i++){//输出p数组即可
if(i != 1) cout << ' ';//调整格式,不调也没事,现在评测器会忽略
cout << p[i];
}
cout << endl;
}
return 0;
}
如果不想用环图用其他方法也可以做出来,只是我觉得这样做思路还算清晰,还有就是,y总yyds