题意:
给定一个2*n的矩阵,
一次操作你可以选择一列,交换该列的两行数字,每一列最多操作一次,
问最少操作多少次,能使得两行都是一个1到n的排列,
输出方案,如果无解输出-1。
数据范围:n<=2e5
解法:
因为题目输入的数都是[1,n]之间的,
那么先判断是否每种数都只出现两次,不为两次则无解.
如果两个相同的数字出现在同一行,
那么这个数字所在的两个列,一定有且只有一个要翻转.
对于每个列x,并查集节点x表示不翻转,x+n表示翻转,
如果两个列x和y必须翻转一个的话,合并x和y+n,合并x+n和y,
如果两个相同的数字补出现在同一行,
那么这个数字所在的两个列,要么都翻转要么都不翻转.
合并x和y,合并x+n和y+n
最后如果存在x和x+n在同一个集合,则无解,否则有解.
还要考虑如何输出方案,我对每个集合维护了一个链表,
输出的时候直接遍历链表即可.
因为要求操作次数最小,选择x和x+n中操作次数少的连通块即可.
ps:
2-sat问题的并查集解法?
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=4e5+5;
vector<int>g[2][maxm];
vector<int>temp[maxm];
vector<int>ans;
int head[maxm],nt[maxm],tail[maxm];
int a[2][maxm];
int mark[maxm];
int pre[maxm];
int cnt[maxm];
int n;
int ffind(int x){
return pre[x]==x?x:pre[x]=ffind(pre[x]);
}
void add(int x,int y){
x=ffind(x),y=ffind(y);
if(x==y)return ;
pre[y]=x;
cnt[x]+=cnt[y];
nt[tail[x]]=head[y];
tail[x]=tail[y];
}
void cal(int x){
for(int i=head[x];i;i=nt[i]){
if(i>n){
ans.push_back(i-n);
mark[i-n]=1;
}else{
mark[i]=1;
}
}
}
signed main(){
ios::sync_with_stdio(0);
int T;cin>>T;
while(T--){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[0][i];
for(int i=1;i<=n;i++)cin>>a[1][i];
//判断数量合法性
map<int,int>tot;
for(int i=1;i<=n;i++){
tot[a[0][i]]++;
tot[a[1][i]]++;
}
int ok=1;
for(auto i:tot){
if(i.second!=2){
ok=0;break;
}
}
if(!ok){
cout<<-1<<endl;
continue;
}
//
for(int i=1;i<=n;i++){
g[0][i].clear();
g[1][i].clear();
}
for(int i=1;i<=n;i++){
g[0][a[0][i]].push_back(i);
g[1][a[1][i]].push_back(i);
}
for(int i=1;i<=n;i++){//init
pre[i]=i;
cnt[i]=0;
head[i]=i;
tail[i]=i;
nt[i]=0;
//
pre[i+n]=i+n;
cnt[i+n]=1;
head[i+n]=i+n;
tail[i+n]=i+n;
nt[i+n]=0;
}
for(int i=1;i<=n;i++){
if(g[0][i].size()==2){//有且只有一个翻转
int x=g[0][i][0],y=g[0][i][1];
add(x,y+n);
add(x+n,y);
}else if(g[1][i].size()==2){//有且只有一个翻转
int x=g[1][i][0],y=g[1][i][1];
add(x,y+n);
add(x+n,y);
}else{//都翻转或都不翻转
int x=g[0][i][0],y=g[1][i][0];
add(x,y);
add(x+n,y+n);
}
}
for(int i=1;i<=n;i++){
if(ffind(i)==ffind(i+n)){//即翻转又不翻转,无解
ok=0;break;
}
}
if(!ok){
cout<<-1<<endl;
continue;
}
ans.clear();
for(int i=1;i<=n;i++){
mark[i]=0;
}
for(int i=1;i<=n;i++){
if(mark[i])continue;
int x=ffind(i),y=ffind(i+n);
if(cnt[x]<cnt[y]){
cal(x);
}else{
cal(y);
}
}
cout<<ans.size()<<endl;
for(auto i:ans){
cout<<i<<' ';
}
cout<<endl;
}
return 0;
}