魔法阵.
题目介绍
给定一个 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 数组,整数之间空格隔开。
如果方案不唯一,则输出任意合理方案即可。
数据范围
1 ≤ T ≤ 100,
2 ≤ n ≤ 2×105,
0≤ fi ≤n,至少两个 fi 为 0。
同一测试点内所有 n 的和不超过 2×10^5。
数据保证有解。
输入样例:
3
5
5 0 0 2 4
7
7 0 0 1 4 0 6
7
7 4 0 3 0 5 1
输出样例:
5 3 1 2 4
7 3 2 1 4 5 6
7 4 2 3 6 5 1
解题思路
题目大意:
给我们一个1-n的全排列,用f(i)来表示,但是有一些元素缺失了,即f(i)=0,我们需要填上这个空缺,并且满足f(i)!=i
方法一:问题是要我们满足缺失位置的f(i)!=i,那我就令缺失位置f(i)=i,然后将其错位,这样就能保住f(i)!=i了,问题来了,当前位置的数,出现过怎么办?
例如样例一
缺失数组 5 0 0 2 4
猜测数组 5 2 3 2 4
我们将所有给出的f(i)全部记录下来,并将其用一个数组标记下来,如果这个数是零的话,我们将其下标存储在一个新的数组里,遍历存储下表数组,如果这个数没有出现过,我们将其存储在一个答案数组中,最后从1-n遍历一遍如果这个数没有出现过,且当答案数组没出现过,就将这个数存储在答案数组中,最后从一到n遍历,如果这个数不是零,我们就输出原数组,否则就输出答案数组(错位输出)
上代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N=200010;
int f[N];//原数组
int ans[N],a[N];//答案数组,中间数组
bool st[N];//标记数组
int main()
{
int T;
scanf("%d",&T);
while(T --)
{
memset(ans, 0, sizeof ans);//多组输入,全部置零
memset(st, 0, sizeof st);
int n;
int cnt=0;
scanf("%d",&n);
for(int i=1;i<=n;i++) //输入原数组,标记出现过的数,如果这个数是零,表示缺失数,记录到中间数组中其值为下表,为了让f(i)=i
{
scanf("%d",&f[i]);
st[f[i]]=true;
if(f[i]==0) a[cnt++]=i;
}
for(int i=0;i<cnt;i++)//遍历中间数组,如果这个数没有出现过,放在答案数组中
if(!st[a[i]]) ans[i]=a[i],st[a[i]]=1;
int j = 0;
for(int i = 1 ;i <=n ;i ++)//从1-n遍历,如果这个数没有出现过,说明它应该在答案数组中,将它存到答案数组中
{
if(!st[i])
{
while(ans[j]) j++;
ans[j] = i;
}
}
j = 0;
for(int i = 1;i <= n ;i ++)//遍历输出,如果原数组是零就将答案数组相错输出,
{
if(!f[i])
{
cout << ans[(j+1) % cnt]<<" ";
j ++;
}
else cout<< f[i] << " ";
}
puts("");
}
return 0;
}
方法二:
本题是将f(i)!=i,那如果相等的话就是一个从1开始的公差为一的单调递增的序列,那我们可以直接将这个序列反转,从n到1,这样就能满足f(i)!=i了,但是如果是下面这个例子该怎么办呢?
7(长度为7)
0 0 0 0 0 0 0
我们填上 7 6 5 4 3 2 1
我们发现f(4)=4,那么我将一个数与这个数交换就ok了,那么就为
4 6 5 7 3 2 1
多个例子发现,最多只会出现一次f(i) =i,如何解决这种情况呢?
我们将缺失的数的下标记录下来,,其大小为m,从大到小排序,将第一个插入的值或者最后一个插入的值进行交换,但由于 f(i) 可能是 f(1) 和 f(m) 其中的一个(也即在 1 或 m 位置出现了冲突),所以需要记录两个位置,选择与其中一个没有冲突的位置互换。
上代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200010;
int a[N],b[N];
bool st[N];
bool cmp(int x,int y)
{
return x>y;
}
int main()
{
int t;
scanf("%d", &t);
while (t -- )
{
memset(st,0,sizeof st);
int n;
int cnt=0;
scanf("%d", &n);
for (int i = 1; i <= n; i ++ )//记录缺失数组,将所有非零的数打上标记
{
scanf("%d", &a[i]);
if(a[i]) st[a[i]]=true;
}
for (int i = 1; i <= n; i ++ )//将所有为零的数的下标存到b数组中
{
if(!st[i]) b[cnt++]=i;
}
sort(b,b+cnt,cmp);//从大到小排列
int j = 0, l, r;
for(int i = 1; i <= n; i++) //找到第一个插入的值和最后一个值
{
if(a[i] == 0)
{
a[i] = b[j],j++;
if(j == 1) l = i;
else if(j == cnt) r = i;
}
}
for (int i = 1; i <= n; i ++ )//交换发生冲突的数
{
if(a[i]==i)
{
if(i!=l) swap(a[i],a[l]);
else swap(a[i],a[r]);
break;
}
}
for (int i = 1; i <= n; i ++ ) printf("%d ",a[i]);
puts("");
}
return 0;
}
方法三
将这个看成是一个图,如果这个f(i)!=0,我们就将i->f(i),连一条边
样例可以看出是1——>5——>4——>2,我们将其构成一个链,我们要保证f(i)!=i,j即没有一条连向自己边,就是没有自环,然后我们将没有出现过的点直接加入这个链中,构成一个环,这样就满足条件了,还有一种情况就是,如果已经构成一个环的话,怎么办呢?我就所有没有出现过的数直接成环
思想很简单,代码超级难
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200010;
int n;
int p[N], q[N];//出边和入边
bool st[N];//标记数组
int main()
{
int T;
scanf("%d", &T);
while (T -- )
{
scanf("%d", &n);
memset(q, 0, sizeof q);
memset(st, 0, sizeof st);
for (int i = 1; i <= n; i ++ )//i->p[i],p[i]的反向边就是i
{
scanf("%d", &p[i]);
q[p[i]] = i;
}
bool flag = false;
for (int i = 1; i <= n; i ++ )
{
if (st[i] || !p[i]) continue;//如果这个点已经处理了,或者这个点是0,我们就跳过
st[i] = true;
int x = i, y = i;
while (p[x] && !st[p[x]])//找到头节点
{
x = p[x];
st[x] = true;
}
while (q[y] && !st[q[y]])//找到尾节点
{
y = q[y];
st[y] = true;
}
if (p[x] == y) continue;//如果联通就直接continue
if (!flag)//处理孤立点
{
flag = true;
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 ++ )
printf("%d ", p[i]);
puts("");
}
return 0;
}
ps:如果不理解第三种方法,详细请看y总的讲解