ABC F(并查集判断是否存在合法交换方案+DFS复原交换方案

F - Swap and Sort
题意:
给定一个 N N N 的排列 P P P m m m 个操作,每个操作给定两个数 a , b a,b a,b,表示 P [ a ] P[a] P[a] P [ b ] P[b] P[b] 可以交换位置,每次可以选定任意操作执行,判断能否把这个排列变成升序的,如果能,则按顺序输出操作的编号,否则输出 − 1 -1 1
思路:
号码牌
ACWing上这道题目的加强版
判断是否存在解,只需要用并查集扫一遍判断当前位置的 P [ i ] P[i] P[i] i i i 是否位于同一个连通块,如果某个位置的 P [ i ] P[i] P[i] i i i 不在一个连通块则无解
这道题 D F S DFS DFS 才是难点所在
首先要明确复原的思路,对于一个连通块,我们每次复原一个点,然后删去,这时候要保证剩下的图是连通的才能复原成功。因此我们可以想到从叶子节点开始复原,每次复原一个叶子节点,复原后剩下的点继续复原时并不需要经过这个叶子节点,复原后的点相当于被删除掉。
因此选定任一连通块,然后不断递归到叶子节点开始依次复原
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 5e5 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
int p[maxn], f[maxn];
bool vis[maxn];
vector <int> e[maxn], ans;
map <pair<int,int>, int> id;

int find(int x){
	return f[x] == x ? x : f[x] = find(f[x]);
}

bool dfs(int x, int y, int fa){// 带fa判断的写法,只能用于遍历树,遍历图如果存在环就会死循环
	if(p[x] == y) return 1;
	for(auto d : e[x]) if(d != fa)
	{
		if(dfs(d, y, x))
		{
			swap(p[x], p[d]);
			ans.push_back(id[{x, d}]);
			return 1;
		}
	}
	return 0;
}
void leaf(int x){
	vis[x] = 1;
	for(auto y : e[x]) if(!vis[y])
		leaf(y);
	dfs(x, x, -1);// 以x位置为起点,寻找p[y]=x的位置y,搜到终点y时会形成一条路径
	// 回溯时不断交换即可完成x位置的复原,这时x就可以删掉了,剩下的点的复原与x位置无关
}

void work()
{
	cin >> n;
	for(int i = 1; i <= n; ++i) cin >> p[i], f[i] = i;
	cin >> m;
	for(int i = 1, a, b; i <= m; ++i){
		cin >> a >> b;
		ll x = find(a), y = find(b);// 下边建图需要用到a,b,因此用临时变量存一下祖先节点
		if(x != y)
		{
			f[x] = y;
			e[a].push_back(b); e[b].push_back(a);
			id[{a, b}] = id[{b, a}] = i;
		}
	}
	for(int i = 1; i <= n; ++i){
		int x = find(i), y = find(p[i]);
		if(x != y){
			cout << -1 << endl;return;
		}	
	}
	for(int i = 1; i <= n; ++i) if(!vis[i])
		leaf(i);
	cout << ans.size() << endl;
	for(auto x : ans) cout << x << " ";
}

int main()
{
	ios::sync_with_stdio(0);
//	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值