圆桌骑士题解

圆桌骑士

前言

怎么每篇博客都有这个

还没完全弄懂,图论的证明好难啊,俗( 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 ikji,路径上的点就可以组成一个点双联通分量,再加入一些点成为 p a r t part part 不影响正确性。


三.
在这里插入图片描述

归纳法:
条件:当前的 p a r t part part 里存在一个奇环。

相关性:若当前这个奇环能不停地加点,一直保证目前的这个部分可以组成奇环,即可证明。

如图,蓝色框住部分为一个奇环,则该奇环上直接相连两点 i i i, j j j 必存在一条路径 k → i k \rightarrow i ki, k → j k \rightarrow j kj 所以 i → k → j i \rightarrow k \rightarrow j ikj,且路径上不存在重复点。

Ⅰ若该路径上的点的个数为偶数,则可以在当前部分中找到一奇环包含 i i i, j j j,再将此奇环和 i → k → j i \rightarrow k \rightarrow j ikj 这条路径合并。
Ⅱ 若该路径上的点的个数为奇数,则该路径就是一个奇环。

综上:初始奇环可以无限拓展,直至全部点都被包含在这个部分里,即证。

posted @ 2021-04-13 17:21  C2022lihan  阅读(63)  评论(0编辑  收藏  举报