acwing第七届蓝桥杯省赛B组 1224. 交换瓶子 图,置换群法

141 阅读3分钟

1224. 交换瓶子 - AcWing题库

image.png image.png code

image.png

image.png

本题的数据范围是1e4,我们用了两层循环,刚好1e8,倘若本题给的数据范围是1e5的话这道题暴力我们就过不了了。

置换群法

首先我们可以发现,这道题并不是要求我们两两交换,我们只需要把a[i]放到其该放的位置即可。

我们将其下标标记出来看看: image.png 我们可以将其抽象为环: 我们发现规律:

一. 一个环有n条边n个点

二. 一条边只有一个出度 (只指向一个点1),且一个点只有1个进度(只被一条边指向)

image.png

现在我们让1这个值指向下标为1的位置:

image.png

用环表示就是:

image.png 因此我们发现每次交换两个点就会分裂出来一个环。

那么我们最终期望的环形图就是所有的点都自成环: image.png

第二种情况就是不同环的进行交换:

image.png

我们发现两个不同的环进行交换必然会进行融环: image.png

总结:对于交换同一个环中的节点,就是裂成两个环。

刚开始有k个环,我们交换同一个环中的节点使其变为一个有序数列,那么就会裂成多个环。

最后,总共会有n个环。最后我们要把整体合并变为一个有序数组,那么就需要合环(消环)。

分裂了多少环就需要消多少环。一共分裂了n-k个环。

实际上也是交换了n-k次。

code

我们可以用并查集去做。

首先读入题目给的顺序到一个新数组a中:

3 1 2 5 4

然后我们的并查集数组存正确的顺序:

1 2 3 4 5

这个时候我们去判断是否在一个环:

我们判断两个点是否在同一个环的方法是判断它们的根是否相同,如果两个点是同一个根,说明它们就在同一个环中,否则就不处于同一个环,那么我们就需要合并环。

我们合并环的方法是将其正确下标赋值给它。

我们判断两个点是否是同一个根的方法就是让它们的根为自身,这样可以直接判断两个数是否相等来确定两个点是否是同一个根。

# include<iostream>
# include<algorithm>

using namespace std;

const int N = 1e4+10;

int n, res;
int a[N], f[N], cnt[N];

int find(int x)
{
    
    cout<<"x: "<<x<<endl;
    cout<<"f[x]: "<<f[x]<<endl;
    if(x!=f[x])   return f[x] = find(f[x]);
    return f[x];
}

int main()
{
    cin>>n;
    for(int i=1; i<=n; ++i)   f[i] = i;             //初始化并查集中的数组f[]
    for(int i=1; i<=n; ++i)   cin>>a[i];            //读入数据

    for(int i=1; i<=n; ++i)
    {
        int fa = find(a[i]), fb = find(i);
        if(fa!=fb)   f[fa] = fb;                    //如果不在一个集合,就合并集合
    
        cout<<"fa: "<<fa<<endl;
        cout<<"fb: "<<fb<<endl;
        cout<<"f[fa]: "<<f[fa]<<endl;
        
    }

    for(int i=1; i<=n; ++i)             //判断回环的个数(并查集的大小)
    {
        int fa = find(a[i]);
        cout<<"  a[i]: "<<a[i]<<endl;
        cout<<"   fa: "<<fa<<endl;
        if(!cnt[fa])   res++;
        cnt[fa]++;
    }

    cout<<n-res<<endl;

    return 0;
}