poj 2438 Children's Dining 哈密顿回路板题 Dirac 定理

缘起

日常浪费生命 poj 2438 Children’s Dining

分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
有2*N个小朋友要坐在一张圆桌上吃饭,但是每两个小朋友之间存在一种关系,即敌人或者朋友,然后需要让你安排一个
座位次序,使得相邻的两个小朋友都不会是敌人.假设每个人最多有N-1个敌人.如果没有输出"No solution!".

【输入】
多样例. 每个样例第一行输入n和m(1 <= n <= 200, 0 <= m <= n (n - 1))
孩子的编号是1~2n
然后是m行. 每行是形如 i,j 的整数对。i != j, 1 <= i, j <= 2 * n 表示i和j是敌人. 同一个测试样例中
一对小朋友之间的关系不会重复给出。 即i,j 给了,j,i就不会给出。最后一个用例以 0 0结束. 不需要处理

【输出】
对每个测试用例,如果满足条件的顺序存在的话,则输出一个序列, 否则打印 "No solution!"

【样例输入】
1 0

2 2
1 2
3 4

3 6
1 2
1 3
2 4
3 5
4 6
5 6

4 12
1 2
1 3
1 4
2 5
2 6
3 7
3 8
4 8
4 7
5 6
5 7
6 8

0 0

【样例输出】
1 2
4 2 3 1
1 6 3 2 5 4
1 6 7 2 3 4 5 8

【限制】
1s

【来源】
poj月赛

将小朋友之间友好视作相邻. 敌对视作不相邻的话. 则给出的m对关系其实就是建立了一个2n个顶点、m条边的无向图. 要找一种合适的顺序就是要找到该无向图的一条哈密顿回路(因为是圆桌). 所谓哈密顿回路指的是图中的一条路径,路径通过每个顶点恰好一次并且最后回到出发点的通路.

所以问题转换为求无向图的哈密顿回路的问题.

而求无向图的哈密顿回路就不得不提到dirac定理.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
由N个顶点构成的无向图G,如果图中任何两个顶点的度数之和>=N, 则此图必存在哈密顿回路.

证明: 首先断言此图连通. 令S和T是G中任意2个顶点. 与它们相邻的顶点集合至多N-2个(N-2个抽屉).
但是度数之和>=N(N个苹果).由抽屉原理,至少存在一个顶点与既与S相邻又与T相邻,令其为v, 则
S-v-T就表明S与T连通. 其次, 此图存在哈密顿回路. 证明方法是构造性的. 即我们准备活生生地
构造出一条哈密顿回路来.
随意挑选两个相邻的顶点S和T. 从S-T这条长度为1的路径为基础,扩展出一条没有重复顶点的尽量长的路径出来.
考虑加入与S相邻的顶点v, v不在S-T上. 则变成 v-S-T. 然后令S=v, 继续扩展——直至这条路径无法扩展为止.
则S-....-T这条路径上没有重复顶点并且所有与S或者T相邻的顶点都在此路径上,否则就还可以扩展(注意,我们可
没说此路径上仅仅由与S或T相邻的顶点构成哦~).
其次如果S和T相邻的话,则一条回路就诞生了. 如果S和T不相邻. 则玄学的, 必定存在S---T这条路径上的两个
相邻的vi和v_{i+1}使得v_{i+1}和S相邻, vi和T相邻. 则只需要将S--T从vi与v_{i+1}处切开, 然后逆转
v_{i+1}--T则得到一个回路.
如果此回路的长度是N的话,算法结束.如果不是n的话,因为前面证明了此图必定连通. 所以一定存在不在S--T
中的一个顶点与S--T上的某个顶点相邻. 假设为W. 则假设是vi和w相邻. 则将S--T在vi处切开并翻转S--vi以及
v_{i+1}---T, 并且将w加入到后半段的最后一个. 然后继续从扩展开始做起. 因为每次确实能拉进入一个元素
所以算法一定有结束的时候. 算法的复杂度显然是O(n^2)的.

显然,dirac定理给出了哈密顿回路存在的充分条件. 本题显然是满足这个充分条件的, 所以不可能输出 “No solution!”

上述算法的图示如下(下图中 vi和T相邻, v_{i+1}和S相邻)

有了上面的图示, 就不难写出本题的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//#include "stdafx.h"

#include <stdio.h>
#include <algorithm>
using namespace std;
#include <string.h>
//#define LOCAL

int n,m,s,t,g[405][405],ans[405],cnt; // g[i][j]=0表示i和j是敌对的, =1表示友善, ans保存哈密顿回路,s是回路最左边的小朋友,t是回路最右边的小朋友,cnt是回路中小朋友的个数
bool v[405]; // true 表示已经在回路中了, false表示不在

void expand() // 扩展回路(通过扩展t)
{
while(1)
{
int i = 1;
while(i<=n)
{
if (!v[i] && g[t][i]) // 如果与t相邻并且尚未加入到回路中
{
ans[cnt++] = i;
v[i] = true;
t = i; // 新入伙的i作为新的t(回路最右端)
break;
}
i++;
}
if (i>n) // 说明当前t已经不能再扩展了(没拉到任何一个相邻且不在回路中的点)
{
return;
}
}
}

void dirac() // 其实就是在模拟上图
{
memset(v, 0, sizeof(v));
cnt = 0; // 已经在回路中的小朋友个数
s=1;
for (t=1;t<=n; t++)
{
if (g[s][t])
{
break;
}
} // 找一个和s相邻的t
ans[cnt++] = s, ans[cnt++] = t; // 初始化回路
v[s] = v[t] = true;
while(1) // 主体循环, 每次通过拉入一个新点来扩展回路ans
{
expand(); // 扩展t
reverse(ans, ans+cnt); // 逆置回路, 则t变成s
swap(s,t); // 再变回来, 使得t依旧是数组ans的最右端
expand(); // 因为expand方法只能扩展最右端的t
// 至此, 我们已经通过最初的s-t,向左向右已经扩展到不能再扩展了,但是此时s--t未必是回路, 因为s和t未必相邻
if (!g[s][t]) // 如果s和t不相邻
{
for (int i = 1; i<cnt-1; i++) // 开始遍历s--t路径上除了s和t之外的点
{
if (g[s][ans[i+1]] && g[t][ans[i]]) // 如果ans[i]和t相邻&&ans[i+1]和s相邻
{
t = ans[i+1];
reverse(ans+i+1, ans+cnt); //雷切+翻转!
}
}
}
if (cnt==n) // 如果已经是哈密顿回路了,则返回
{
return;
}
for (int i = 1; i<=n;i++)
{
if (!v[i]) // 寻找s--t回路以外的点
{
for (int j = 1; j<cnt-1; j++)
{
if (g[ans[j]][i]) // 找到s--t上一个点ans[j]和回路之外的i相邻
{
v[i] = true; // 入伙!
s = ans[j-1];
reverse(ans, ans+j);
t = ans[j];
reverse(ans+j, ans+cnt);
ans[cnt++] = i;
t = i;
goto xxx; // 找到一个就不玩了
}
}
}
}
xxx:;
}
}

int main()
{
#ifdef LOCAL
freopen("d:\\data.in", "r", stdin);
//freopen("d:\\my.out", "w", stdout);
#endif
while(~scanf("%d%d", &n, &m), n||m)
{
n<<=1; // 小朋友有2n个
for (int i = 1; i<=n;i++)
{
for (int j = i; j<=n; j++)
{
g[i][j] = g[j][i] = i==j?0:1; // 一开始小朋友之间都是友善的
}
}
while (m--)
{
int x,y;
scanf("%d%d", &x, &y);
g[x][y] = g[y][x] = 0; // x和y敌对起来
}
dirac();
for (int i = 0; i<n; i++)
{
if (i)
{
putchar(' ');
}
printf("%d", ans[i]);
}
puts("");
}
return 0;
}

ac情况

Status Accepted
Time 172ms
Memory 748kB
Length 2235
Lang C++
Submitted 2019-09-06 07:27:16
Shared
RemoteRunId 20836982