罪人挽歌 题解

发布时间 2023-09-18 15:23:04作者: 霜木_Atomic

罪人挽歌 题解

简化题意:

给定 \(n\) 个二元组,第 \(i\) 个二元组的值为 \((a, b)\)保证任意两个二元组都不相同

求是否存在一个二元组的排列,使得这些二元组满足对于任意 \(1 \leq i < n\),有 \(A_i = A_{i+1}\)\(B_i = B_{i+1}\),且对于任意 \(2 \leq i < n\)\(A_{i-1} = A_i = A{i+1}\)\(B_{i-1} = B_i = B_{i+1}\) 都不成立。如果有解,还需找到字典序最小的解。

思路:

首先我们可以列出一个合法的方案,如下:

a b
c b
c e
a e
...

我们把相同的字母连起来,发现了没?是不是很像欧拉路!

我们可以把 \(a\)\(b\) 连边(当然 \(b\) 必须加上 \(n\) 来区分左右部点),这样就形成了一个二分图。

那么,一个序列有解,当且仅当去掉所有大小为 \(1\) 的连通块后,仅剩下一个连通块,且存在一条欧拉路径。答案就是字典序最小的欧拉路径。

我们来证明一下这样做的正确性。必要性很显然,因为如果存在一个解,那么将上下相同的点连起来,形成的就是一条欧拉回路。

至于充分性,考虑反证。假设一条欧拉路径无解,则形成的序列必然为以下形式:

a b
c b
c b

而题目保证任意两个二元组都不相同,因此假设错误,原命题成立。

至于保证字典序最小,我们贪心的选编号更小的边来跑欧拉路即可。这里可以利用邻接表存图是倒序的性质,倒着加边,这样第一个遍历的边就一定是编号最小的。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+100;

inline int read() {
	int x = 0; char ch = getchar();
	while(ch<'0' || ch>'9') ch = getchar();
	while(ch>='0'&&ch<='9') x = x*10+(ch-48), ch = getchar();
	return x;
}
//欧拉回路?
//不一定是回路。
//把出边按先后顺序排序一下(
//额……邻接表是倒着存的图,那就反着加边(
//并查集维护一下联通块
int head[N], tot = 1;
struct node{
	int nxt, to, id;
}edge[N<<2];
void add(int u, int v, int id){
	edge[++tot].nxt = head[u];
	edge[tot].to = v;
	edge[tot].id = id;
	head[u] = tot;
}
int de[N];
vector<int> vc[N];
int totc;
int col[N];
int n;
struct DSU {
	int fa[N];
	void init() {
		for(int i = 1; i<=n*2; ++i) fa[i] = i;
	} 
	int find(int x) {
		if(x != fa[x]) fa[x] = find(fa[x]);
		return fa[x];
	}
	void merge(int x, int y) {
		x = find(x), y = find(y);
		if(x == y) return;
		fa[x] = y;
	}
}dsu;
int a[N], b[N];
int vis[N];//记录每个点最早有边的时刻。
int visc[N];//记录每个连通块最早有边的时刻。 
bool vise[N<<2];

int ans[N], tota;
int st, ed;
int edid = -1;
void dfs(int u, int from) {
//	cout << de[ed] << endl;
	for(int &i = head[u]; i; i = edge[i].nxt) {
		if(vise[i]) continue;
		vise[i] = vise[i ^ 1] = 1;
		// ans[++tota] = edge[i].id;
//		if(ans[tota] == 240) printf("%d\n", tota);
		dfs(edge[i].to, edge[i].id);
	}
	ans[++tota] = from;
}
bool flag;
void work(int id) {
	if(vc[id].size() == 1) return;
	if(flag) {
		puts("No");
		exit(0);
	}
	flag = 1;
	st = -1, ed = -1; int cnt = 0;
	for(int i: vc[id]) {
		if(de[i] & 1) {
			if(st == -1){
				st = i;
			} else ed = i;
			++cnt;
			if(cnt > 2) {
				puts("No");
				exit(0);
			}
		}
	}
	if(cnt == 1) {
		puts("No");
		exit(0);
	}
	if(cnt == 2) {
		if(vis[st] > vis[ed]) swap(st, ed);
		dfs(st, 0);
	} else{
		ed = -1;
		st = vc[id][0];
		for(int i: vc[id]) {
			if(vis[i] < vis[st]) {
				st = i;
			}
		}
		dfs(st, 0);
	}
}
int q[N], top;
bool cmp(int x, int y) {
	return visc[x] < visc[y];
}
int main() {
	n = read();
	dsu.init();
	for(int i = 1; i<=n; ++i) {
		a[i] = read(), b[i] = read() + n;
		if(!vis[a[i]]) {
			vis[a[i]] = i;
		}
		if(!vis[b[i]]) {
			vis[b[i]] = i;
		}
		dsu.merge(a[i], b[i]);
	} 
	for(int i = n; i>=1; --i) {
		add(a[i], b[i], i);
		add(b[i], a[i], i);
		++de[a[i]], ++de[b[i]];
	}
	for(int i = 1; i<=n*2; ++i) {
		int fi = dsu.find(i);
		if(col[fi]) {
			visc[col[fi]] = min(vis[i], visc[col[fi]]);
			vc[col[fi]].push_back(i);
		} else{
			col[fi] = ++totc;
			q[++top] = totc;
			visc[totc] = vis[i];
			vc[totc].push_back(i);
		}
	}
	sort(q+1, q+top+1, cmp);
	for(int T = 1; T<=top; ++T){
		work(q[T]);
	}
	puts("Yes");
	// if(tota < n) ans[++tota] = edid;
	for(int i = 1, j = tota-1; i<=n; ++i, --j) {
		if(i > 1) putchar(' ');
		printf("%d", ans[j]);
	}
	putchar('\n');
	return 0;
}