欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。
题目大意
https://codeforces.com/contest/1385/problem/G
给定一个2*n的表格,里面放的都是1-n的数字。
问题能否能过对调某些列的数字,使得两行都成为1-n的排列。
分析&思路
对于某列换与不换,可以使用2sat来表示,0值表示不换,1值表示要换。
首先要保证所有1-n数字都出现2次,否则不可能构造出2个排列。
然后是验证2sat。
条件设置:
- 如果某一数字2次都出现在同一行,那么有且仅有一个要换。
- 如果某一数字2次出现在不同行且不同列,那么要同时换可不换。
题目要求的是对调最少的列数,这里要用到一个结论。
如果列a的结果会对列b的结果产生影响,那么a和b肯定在一个极大连通子图中。
那么我们就可以对连通图进行缩点,每个连通图的缩点,都会对应一个反向图。
一个缩点的所有列也会出现在反图中,即a列不对调会对应一个缩点,
a列对调会对应另一个缩点,如果a列不对调会导出b列不对调,
那么a列对调肯定要导出b列对调。
AC代码
#include <iostream>
#include <stdio.h>
#include <queue>
#include <string.h>
#include <stack>
#include <vector>
#include <map>
#include <algorithm>
#include <assert.h>
using namespace std;
const int N = 6e5 +10;
const int E = 6e6+10;
// 边属性
class Edge {
public:
int toVertex;
int nextEdge;
};
// 点属性
class Node {
public:
int head;
int indu;
};
class Graph {
public:
Edge edges[E];
Node nodes[N];
int usedEdge=0;
Graph() {
usedEdge = 0;
}
void initEdge(int n) {
for(int i=0;i<=n;++i) {
nodes[i].head=-1;
nodes[i].indu = 0;
}
usedEdge = 0;
}
void addEdge(int a, int b) {
if(a==b) return;
edges[usedEdge].nextEdge = nodes[a].head;
nodes[a].head = usedEdge;
edges[usedEdge].toVertex = b;
nodes[b].indu++;
usedEdge++;
// cout<<"add edge: "<<a<<","<<b<<endl;
}
int dfn[N], low[N];
stack<int> st;
int deep, sum;
int color[N];
void initTarjan(int n) {
deep = 0;
sum=0;
memset(dfn, 0,sizeof(int)*n);
memset(low, 0,sizeof(int)*n);
memset(color, 0,sizeof(int)*n);
}
void tarjan(int u) {
dfn[u] = ++deep;
low[u] = deep;
st.push(u);
for(int i=nodes[u].head;i>=0;i = edges[i].nextEdge) {
int v = edges[i].toVertex;
if(!dfn[v]) {
tarjan(v);
low[u] = min(low[u], low[v]);
} else if(!color[v]) {
low[u] = min(low[u], dfn[v]);
}
}
if(dfn[u]==low[u]) {
color[u] = ++sum;
while(st.top()!=u) {
int v = st.top();
st.pop();
color[v]=sum;
}
st.pop();
}
}
map<int, int> loc;
bool topo(int n, bool deb) {
queue<int>qu;
for(int i=1;i<=n;++i) {
if(nodes[i].indu==0) {
qu.push(i);
}
}
int l=0;
while(!qu.empty()) {
int f = qu.front();
loc[f]=++l;
qu.pop();
for(int i=nodes[f].head;i>=0;i=edges[i].nextEdge) {
int v = edges[i].toVertex;
nodes[v].indu--;
if(nodes[v].indu==0) qu.push(v);
}
}
if(deb) cout<<l<<endl;
return l==n;
}
};
Graph og;
// false not swap
void either(int i, bool si, int j, bool sj) {
og.addEdge(i*2+(si^1), 2*j+sj);
og.addEdge(j*2+(sj^1), 2*i+si);
}
void must(int i, bool sel) {
og.addEdge(i*2+(sel^1), i*2+sel);
}
void solve() {
int n;
scanf("%d", &n);
map<int, int> m1;
map<int, int> m2;
map<int, int> cc;
int a;
og.initEdge(2*n+10);
for(int i=0;i<n;++i) {
scanf("%d", &a);
cc[a]++;
if(m1.count(a)>0) {// 数字在同一行,有且仅有一个要换
either(m1[a], 1, i, 1);
either(i, 0, m1[a], 0);
m1.erase(a);
}else m1[a] = i;
}
for(int i=0;i<n;++i) {
scanf("%d", &a);
cc[a]++;
if(m2.count(a)>0) {// 数字在同一行,有且仅有一个要换
either(m2[a], 1, i, 1);
either(i, 0, m2[a], 0);
m2.erase(a);
}else m2[a] = i;
}
for(int i=1;i<=n;++i) {
if(cc[i]!=2){ // 每个数字必须出现2次。
puts("-1");
return;
}
if(m1.count(i)==0) continue;
if(m1[i]==m2[i])continue; // 相同数字在同一列,换与不换都可以
// 相同数字在不同列且不同行,要换就同时换。
either(m1[i], 1, m2[i], 0);
either(m2[i], 1, m1[i], 0);
}
og.initTarjan(2*n+10);
for(int i=0;i<2*n;++i) {
if(!og.dfn[i])og.tarjan(i);
}
int cnt=0;
vector<int> pos;
map<int, vector<int>> change;
for(int i=0;i<n;++i) {
if(og.color[2*i]==og.color[2*i+1]){
puts("-1");
return;
}
change[og.color[2*i+1]].push_back(i);// 缩点,连通图中要变的列统计出来。
}
vector<bool> vis(2*n, false);
for(int i=0;i<2*n;++i) {
if(vis[og.color[i]])continue;
int c1 = og.color[i], c2 = og.color[i^1];
vis[c1]=true;
vis[c2]=true;
if(change[c1].size()< change[c2].size()){
cnt += change[c1].size();
for(auto p: change[c1]) pos.push_back(p);
}else {
cnt += change[c2].size();
for(auto p: change[c2]) pos.push_back(p);
}
}
printf("%d\n", cnt);
for(int i=0;i<pos.size();++i) {
if(i)putchar(' ');
printf("%d", pos[i]+1);
}
puts("");
}
int main() {
int t;
scanf("%d", &t);
while(t--) {
solve();
}
return 0;
}
/*
3
1 2 1
3 3 2
1
1 2 3
4
1 2 2 1
3 4 3 4
4
3 4 3 4
1 2 2 1
10
1 2 1 4 5 6 7 8 9 9
3 3 2 4 5 6 7 8 9 10
*/
本人码农,希望通过自己的分享,让大家更容易学懂计算机知识。