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;
}
posted @ 2021-07-22 22:05  inss!w!  阅读(60)  评论(0编辑  收藏  举报