圆桌骑士题解
前言
怎么每篇博客都有这个
还没完全弄懂,图论的证明好♂难啊,俗(
w
o
˘
w\breve{o}
wo˘)话说的好:图论代码百余行,证明上万行。
题解部分
(1)实现部分
attention:为了描述方便,以
p
a
r
t
part
part 代替 “极大点连通分量”
为什么把实现部分放在前面呢,因为估计没人看证明
0.建图,将所有骑士抽象成点,再将每对不互相怨恨的骑士连上一条无向边
①:题目的理解:找到所有不在任何一个奇环上的点。
1.得到每个 p a r t part part
2.判断这个 p a r t part part 是不是二分图。
①:若是二分图,则不存在奇环,所以这个 p a r t part part 中没有一个满足要求
②:若不是二分图,则存在至少一个奇环,可以证明这个 p a r t part part 中的每一个点都是满足要求的,将这个 p a r t part part 中的每个点都打上标记。
3.统计答案,累加所有没有打标记的点。
没错,就这
nigu 管理员是不是疏于工作,没有及时更新标签
怎么会呢, n i g u nigu nigu 的管理员都是兢兢业业的,所以这道题成为紫题是有道理的,虽然代码不麻烦,但想到这个思路却十分困难
(2)参考代码
#include <map>
#include <set>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define LL long long
#define ULL unsigned long long
template <typename T> int read (T &x) {x = 0; T f = 1;char tem = getchar ();while (tem < '0' || tem > '9') {if (tem == '-') f = -1;tem = getchar ();}while (tem >= '0' && tem <= '9') {x = (x << 1) + (x << 3) + tem - '0';tem = getchar ();}x *= f; return 1;}
template <typename T> void write (T x) {if (x < 0) {x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
template <typename T> T Max (T x, T y) { return x > y ? x : y; }
template <typename T> T Min (T x, T y) { return x < y ? x : y; }
template <typename T> T Abs (T x) { return x > 0 ? x : -x; }
const int Maxn = 1e3;
const int Maxm = 1e6;
int n, m;
int len, Head[Maxn + 5];
struct edge {
int to, Next;
}e[Maxm * 2 + 5];
void add (int x, int y) {
e[++len].to = y;
e[len].Next = Head[x];
Head[x] = len;
}
int dfn[Maxn + 5], low[Maxn + 5], timestamp;
int Top, dcc_cnt;
pair <int, int> st[Maxm + 5];
vector <pair <int, int> > block[Maxm + 5];
void Tarjan (int u, int from) {
dfn[u] = low[u] = ++timestamp;
for (int i = Head[u]; i; i = e[i].Next) {
int v = e[i].to;
if ((i ^ 1) == from) continue;
if (dfn[v] == 0) {
st[++Top] = make_pair (u, v);
Tarjan (v, i);
low[u] = Min (low[u], low[v]);
if (low[v] >= dfn[u]) {//是割点(不包含根节点)
dcc_cnt++; block[dcc_cnt].clear ();
pair <int, int> tem;
do {
tem = st[Top--];
block[dcc_cnt].push_back (tem);
}while (tem.first != u || tem.second != v);
}
}
else {
if (dfn[v] <= dfn[u])
st[++Top] = make_pair (u, v);
low[u] = Min (low[u], dfn[v]);
}
}
}
int colour[Maxn + 5];
bool vis[Maxn + 5];
bool check;
void dfs (int u) {
for (int i = Head[u]; i; i = e[i].Next) {
int v = e[i].to;
if (vis[v] == 0) {
colour[v] = colour[u] ^ 1;
vis[v] = 1;
dfs (v);
}
else if (colour[v] != (colour[u] ^ 1)) {
check = 1;
}
}
}
bool can_leave[Maxn + 5];
bool w[Maxn + 5][Maxn + 5];
signed main () {
while (~scanf ("%d %d", &n, &m) && n + m) {
len = 1; dcc_cnt = timestamp = 0;
memset (w, 0, sizeof w);
memset (dfn, 0, sizeof dfn);
memset (low, 0, sizeof low);
memset (Head, 0, sizeof Head);
memset (can_leave, 0, sizeof can_leave);
for (int i = 1; i <= m; i++) {
int x, y; read (x); read (y);
if (x == y) continue;
w[x][y] = w[y][x] = 1;
}
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (w[i][j] == 1) continue;
add (i, j); add (j, i);
}
}
for (int i = 1; i <= n; i++) {
if (dfn[i] == 0)
Tarjan (i, -1);
}
for (int u = 1; u <= dcc_cnt; u++) {
len = 1; check = 0;
memset (Head, 0, sizeof Head);
memset (vis, 0, sizeof vis);
for (int i = 0; i < block[u].size (); i++) {
add (block[u][i].first, block[u][i].second);
add (block[u][i].second, block[u][i].first);
}
colour[block[u][0].first] = 0;
vis[block[u][0].first] = 1;
dfs (block[u][0].first);
if (check == 1) {
for (int i = 0; i < block[u].size (); i++) {
can_leave[block[u][i].first] = 1;
can_leave[block[u][i].second] = 1;
}
}
}
int ans = 0;
for (int i = 1; i <= n; i++)
if (can_leave[i] == 0)
ans++;
write (ans); putchar ('\n');
}
return 0;
}
(3).证明部分
需要证明的有如下三个命题:
1.每一个 p a r t part part 中,若不存在奇环,则整个 p a r t part part 没有满足要求的点。(奇环不经过其他 p a r t part part)
2.每个奇环都至少被一个 p a r t part part 完全包含。
3.每一个 p a r t part part 中,若存在奇环,则整个 p a r t part part 都满足要求。
一. 由于这个 p a r t part part 中没有奇环,所以满足要求的点的数量为零,换句话讲,就是没有点满足要求。
二.
反证法:若奇环中的所有点能加入一个或多个 p a r t part part 中,即可证明。
如图,考虑两个不同的 p a r t part part ( u u u, v v v),只在 u u u 和 v v v 中讨论奇环
由于此结论具有轮换性,不妨只考虑 p a r t : u part:u part:u
由于 p a r t : u part:u part:u 中联通,则必有一条路径 i → k → j → i i \rightarrow k \rightarrow j \rightarrow i i→k→j→i,路径上的点就可以组成一个点双联通分量,再加入一些点成为 p a r t part part 不影响正确性。
三.
归纳法:
条件:当前的
p
a
r
t
part
part 里存在一个奇环。
相关性:若当前这个奇环能不停地加点,一直保证目前的这个部分可以组成奇环,即可证明。
如图,蓝色框住部分为一个奇环,则该奇环上直接相连两点 i i i, j j j 必存在一条路径 k → i k \rightarrow i k→i, k → j k \rightarrow j k→j 所以 i → k → j i \rightarrow k \rightarrow j i→k→j,且路径上不存在重复点。
Ⅰ若该路径上的点的个数为偶数,则可以在当前部分中找到一奇环包含
i
i
i,
j
j
j,再将此奇环和
i
→
k
→
j
i \rightarrow k \rightarrow j
i→k→j 这条路径合并。
Ⅱ 若该路径上的点的个数为奇数,则该路径就是一个奇环。
综上:初始奇环可以无限拓展,直至全部点都被包含在这个部分里,即证。