题意
思考
简要题意就是给定一个排列,每个元素有两个对应关系,问你是否能将该排列转换为另一个排列,并使之字典序最小,如果不考虑字典序的话,这题就是裸的一道求二分图完美匹配的题,那么我们该如何考虑字典序呢?
我们可以按字典序暴力枚举左边的点与右边的哪个点相匹配,再跑二分图。
实际上我们可以不这样做,二分图匹配的过程实际上是贪心的过程,我们为了满足当前点,有时会让已经匹配的点更换匹配点,那么我们让字典序小的靠前选择,并且下往上倒着匹配,就可以满足字典序最小了。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int M = 100010;
const int N = 100010;
int read(){
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x * f;
}
struct node{
int nxt, to;
}edge[M << 1];
int head[N], num;
void build(int from, int to){
edge[++num].nxt = head[from];
edge[num].to = to;
head[from] = num;
}
int match[N], vis[N];
int n;
int ANS[N];
bool dfs(int u){
for(int i=head[u]; i; i=edge[i].nxt){
int v = edge[i].to;
if(!vis[v]){
vis[v] = 1;
if(match[v] == -1 || dfs(match[v])){
match[v] = u; ANS[u] = v; return 1;
}
}
}
return 0;
}
int main(){
n = read();
for(int i=0; i<=2*n+1; i++) match[i] = -1;
for(int i=0; i<=n-1; i++){
int d = read();
int a = (i + d) % n + n, b = (i - d + n) % n + n;
if(a < b) swap(a, b);
build(i, a);
build(i, b);
}
int ans = 0;
for(int i=n-1; i>=0; i--){
memset(vis, 0, sizeof(vis));
if(!dfs(i)){
puts("No Answer");
return 0;
}
}
for(int i=0; i<=n-1; i++){
cout<<ANS[i] - n<<" ";
}
return 0;
}
总结
有几点要注意的:
- 点是从零开始编号的,所以 (match) 数组初始化为 (-1) 而不是 (0)
- 有一个小技巧,对每个点 (u) 进行匹配的时候,不用重新清零 (vis[]) 数组,只用在标记时将其标记为当前匹配点的编号( (u) )就好~