Codeforces 1385G Columns Swaps 题解 [ 蓝 ] [ 扩展域并查集 ] [ 二分图最大权匹配 ] [ 基环树建模 ]

Columns Swaps

经典的图论问题,典中典了。

首先判掉无解,如果有一个数没有出现两次,则一定无解。在此基础上我们也可以得出一个转化:只需要保证序列 \(a_1\) 是一个排列,那么 \(a_2\) 自然也是一个排列了。

Sol.1 二分图最大权匹配

如果你科技学傻了,那么这就是一种最无脑的做法。

将每个数字看做二分图的左部点,每个位置看做二分图的右部点,每个数字朝对应位置连边。如果这个数字最初出现在 \(a_1\) 中,边权为 \(1\),否则边权为 \(0\)。因为每个数字必定会和某个位置匹配,所以答案一定是一个排列。记求出的最大权为 \(x\),则答案即为 \(n -x\)

或者说还可以转化为最小割集合划分模型之后跑网络流,都是差不多的。时间复杂度取决于你二分图匹配怎么实现,如果用 Dinic 就是 \(O(n\sqrt n)\) 的。

Sol.2 基环树建模

参考“连续攻击游戏”的 trick,每个物品的属性要么是 \(a\),要么是 \(b\),可以将该物品视为边,\(a, b\) 等数字视为点,连边之后发现,因为每个数字最多出现 \(2\) 次,所以会形成一颗基环树 / 基环树森林。

我们对形成的每颗基环树分别考虑,先枚举某条特定的边是选 \(u\) 还是选 \(v\),选完之后容易发现为了形成一个排列,那么此后的操作一定是唯一的。于是算一下两者的花费,取最小值即可。

时间复杂度 \(O(n)\)

Sol.3 扩展域并查集

如果两个相同的数同时出现在 \(a_1\) 中,那么说明这两个位置必然有一个没有被反转、且另一个被反转。于是可以直接上扩展域并查集,求出每个连通块反转的最小值即可。

时间复杂度 \(O(n)\),代码难度应该是最小的了。

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 400005;
int n, tot[N], a[N], b[N], prea[N], preb[N], tag[N];
int fa[N], sz1[N], sz2[N];
void init()
{
    for(int i = 1; i <= n; i++) fa[i] = i, sz1[i] = 1, sz2[i] = 0, tag[i] = 0;
    for(int i = n + 1; i <= 2 * n; i++) fa[i] = i, sz2[i] = 1, sz1[i] = 0, tag[i] = 0;
}
int findf(int x)
{
    if(fa[x] != x) fa[x] = findf(fa[x]);
    return fa[x];
}
void combine(int x, int y)
{
    int fx = findf(x), fy = findf(y);
    if(fx == fy) return;
    fa[fx] = fy;
    sz1[fy] += sz1[fx];
    sz2[fy] += sz2[fx];
}
void solve()
{
    cin >> n;
    for(int i = 1; i <= n; i++) tot[i] = prea[i] = preb[i] = 0;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i];
        tot[a[i]]++;
    }
    for(int i = 1; i <= n; i++)
    {
        cin >> b[i];
        tot[b[i]]++;
    }
    for(int i = 1; i <= n; i++)
    {
        if(tot[i] != 2)
        {
            cout << -1 << "\n";
            return;
        }
    }
    init();
    for(int i = 1; i <= n; i++)
    {
        if(prea[a[i]])
        {
            combine(prea[a[i]] + n, i);
            combine(prea[a[i]], i + n);
        }
        if(preb[a[i]])
        {
            combine(preb[a[i]], i);
            combine(preb[a[i]] + n, i + n);
        }        
        if(prea[b[i]])
        {
            combine(prea[b[i]], i);
            combine(prea[b[i]] + n, i + n);
        }
        if(preb[b[i]])
        {
            combine(preb[b[i]] + n, i);
            combine(preb[b[i]], i + n);
        }                
        prea[a[i]] = i;
        preb[b[i]] = i;
    }
    int ans = 0;
    for(int i = 1; i <= n; i++)
    {
        if(findf(i) != i) continue;
        if(sz1[findf(i)] < sz2[findf(i)])
        {
            tag[findf(i)] = 1;
            ans += sz1[findf(i)];
        }
        else
        {
            tag[findf(i + n)] = 1;
            ans += sz2[findf(i)];
        }
    }
    cout << ans << "\n";
    for(int i = 1; i <= n; i++)
        if(tag[findf(i)])
            cout << i << " ";
    cout << "\n";
}
int main()
{
    //freopen("sample.in", "r", stdin);
    //freopen("sample.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while(t--) solve();
    return 0;
}
posted @ 2025-10-02 01:30  KS_Fszha  阅读(6)  评论(0)    收藏  举报