[NOI2009]变换序列

题意

Here

思考

简要题意就是给定一个排列,每个元素有两个对应关系,问你是否能将该排列转换为另一个排列,并使之字典序最小,如果不考虑字典序的话,这题就是裸的一道求二分图完美匹配的题,那么我们该如何考虑字典序呢?

我们可以按字典序暴力枚举左边的点与右边的哪个点相匹配,再跑二分图。

实际上我们可以不这样做,二分图匹配的过程实际上是贪心的过程,我们为了满足当前点,有时会让已经匹配的点更换匹配点,那么我们让字典序小的靠前选择,并且下往上倒着匹配,就可以满足字典序最小了。

代码

#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;
}

总结

有几点要注意的:

  1. 点是从零开始编号的,所以 \(match\) 数组初始化为 \(-1\) 而不是 \(0\)
  2. 有一个小技巧,对每个点 \(u\) 进行匹配的时候,不用重新清零 \(vis[]\) 数组,只用在标记时将其标记为当前匹配点的编号( \(u\) )就好~
posted @ 2018-11-06 14:32  alecli  阅读(85)  评论(0编辑  收藏  举报