当前位置: 首页 > news >正文

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;
}
http://www.hskmm.com/?act=detail&tid=22674

相关文章:

  • 72. 编辑距离
  • PlantUML 完整教程:从入门到精通
  • 你妈的
  • test6
  • test7
  • 学习java的第三天
  • vscode github 推送失败
  • 信奥大联赛周赛(提高组)#2515-S 赛后盘点
  • 虚拟机仅主机模式下使用ssh远程连接Linux(EHEL8)连接慢,需要等待30秒以上
  • VLC Player插件和自动激活
  • 第七天
  • logback.xml 常用配置详解 - Higurashi
  • MySQL COUNT(*)性能对比:MyISAM为何比InnoDB快?全面解析与优化方案
  • 2025.10.1总结
  • 子结构判断
  • 使用 Go 进行验证码识别
  • 使用 Rust 进行验证码识别
  • 使用 Swift 进行验证码识别
  • torchtext与torch版本对应关系
  • Python错题集
  • 火狐浏览器新页覆盖旧页解决方法
  • msi主板,windows11,mbr转gpt后,提示0xc000000e1,无法进入系统
  • MAUI下热重载不生效
  • AdGuard广告拦截器APP v4.11.63 / 4.13.7 Nightly 修改版
  • 在疼痛中锚定前路
  • Chrome在Android上Speedometer性能翻倍的技术揭秘
  • 《电路基础》第四章学习笔记
  • 题解:AT_arc184_d [ARC184D] Erase Balls 2D
  • US$39 PowerBox for KTM JTAG for Hitachi
  • 最小二乘问题详解2:线性最小二乘求解