G. Columns Swaps(并查集)

G. Columns Swaps

题目大意:

两行 n n n列,一次操作可以交换同列两个数,要求最小操作次数使两行都是 1 − n 1-n 1n的排列。

前置知识:

2-sat

思路:

每个数都要出现两次这是必要条件。
如果一个数两次出现在同一行,必须一假一真
如果一个数两次出现在不同行,必须同真同假
可以发现本题是无向边(和第几次出现无关),可以用并查集实现。
编号 i i i 表示真 i + n i+n i+n表示假。
并查集的合并:

			for(int i=1;i<=n;i++){  // li 表示第i次出现的列,hi同理
				if(h1[i]==h2[i]){  // 同一行
					work(l1[i],l2[i]+n);
					work(l2[i],l1[i]+n);
				}
				else if(l1[i]!=l2[i]){  // 不同行不同列
					work(l1[i],l2[i]);
					work(l1[i]+n,l2[i]+n);
				}
			}

所有条件的真假不在同一连通块内就一定有解。
操作次数最小:
并查集在合并时,维护每个连通块内真的个数。(也就是操作数)
当前的连通块如果没有作决策选择真假两个块里最小的即可。

				queue<int> ans;
				for(int i=1;i<=n;i++){
					int s1=find(i),s2=find(i+n); // 找块
					if(vis[s1]==0){  // 还没确定
						if(_size[s1]<_size[s2]){  // 选真更好
							vis[s1]=1;
							vis[s2]=-1;
						}
						else{
							vis[s2]=1;
							vis[s1]=-1;
						}
					}
					if(vis[s1]==1) ans.push(i); 
				}

注意初始化,二倍空间。

Code:

#include <iostream>
#include <map>
#include <set>
#include <queue>
#include <stack>
#include <algorithm>
#include <vector>
#include <string>
#include <iomanip>
#include <cmath>
#include <ctime>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <climits>
//#include <unordered_map>
#define guo312 std::ios::sync_with_stdio(false), cin.tie(0), cout.tie(0)
#define ll long long
#define Inf LONG_LONG_MAX
#define inf INT_MAX
#define endl "\n"
#define PI 3.1415926535898
using namespace std;
const int N=4e5+10;
int n;
int a[2][N];
map<int,int> mp;
int h1[N],h2[N],l1[N],l2[N];

int flag[N],_size[N],vis[N];
void init(){
	for(int i=1;i<=n*2;i++){
		flag[i]=i,_size[i]=1,vis[i]=0;
		if(i>n) _size[i]=0;
	}
}

int find(int x){
	if(flag[x]==x){
		return x;
	}
	else{
		return flag[x]=find(flag[x]);
	}
}

void work(int x,int y){
	int s1=find(x),s2=find(y);
	if(s1==s2) return ; 
	_size[s1]+=_size[s2],flag[s2]=s1;
}

bool ok1(){
	for(auto it:mp){
		if(it.second!=2) return 0;
	}
	return 1;
}

bool ok2(){
	for(int i=1;i<=n;i++){
		int s1=find(i),s2=find(i+n);
		if(s1==s2) return 0;
	}
	return 1;
}

int main(){
guo312;
	int t; cin>>t; 
	while(t--){
		cin>>n; mp.clear(); init();
		for(int j=0;j<=1;j++){
			for(int i=1;i<=n;i++){
				cin>>a[j][i];
				mp[a[j][i]]++;
				if(mp[a[j][i]]==1){
					h1[a[j][i]]=j;
					l1[a[j][i]]=i;
				}
				else{
					h2[a[j][i]]=j;
					l2[a[j][i]]=i;
				}
			}
		}
		if(ok1()){
			for(int i=1;i<=n;i++){
				if(h1[i]==h2[i]){
					work(l1[i],l2[i]+n);
					work(l2[i],l1[i]+n);
				}
				else if(l1[i]!=l2[i]){
					work(l1[i],l2[i]);
					work(l1[i]+n,l2[i]+n);
				}
			}
			if(ok2()){
				queue<int> ans;
				for(int i=1;i<=n;i++){
					int s1=find(i),s2=find(i+n);
					if(vis[s1]==0){
						//cout<<_size[s1]<<" "<<_size[s2]<<" "<<s1<<" "<<s2<<endl; 
						if(_size[s1]<_size[s2]){
							vis[s1]=1;
							vis[s2]=-1;
						}
						else{
							vis[s2]=1;
							vis[s1]=-1;
						}
					}
					if(vis[s1]==1) ans.push(i); 
				}
				cout<<ans.size()<<endl;
				while(!ans.empty()){
					int s=ans.front(); ans.pop();
					cout<<s<<" "; 
				}
				cout<<endl;
			}
			else{
				cout<<"-1"<<endl;
			}
		}
		else{
			cout<<"-1"<<endl;
		}
	}
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

要用bug来打败bug

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值