LJF 清北图论 Day1 笔记

LJF 清北图论 Day1 笔记

一、从一个简单的问题开始

现在有奇数个人,两两间可能认识或者不认识,证明永远存在一个人认识偶数个人

由于两个人只有认识和不认识两种关系,也叫做布尔关系,我们可以把这个问题抽象成图的问题

那什么是图?

图由点集和边集构成,确定有哪些点和哪些边就可以确定一张图

我们称一个点连接的点数为这个点的度数

那么这个题就转换为了,给定一张奇数个点的图,求证存在一个度数为偶数的点

我们考虑拓展一下命题,可以得到如下:

给定一张奇数个点的图,求证存在奇数个度数为偶数的点

考虑变化一下,得到如下命题:

给定一张奇数个点的图,求证存在偶数个度数为奇数的点

证明出来这个命题就可以一路推到上面

考虑怎么证明

我们求度数和,每条边的贡献是 $ 2 $,所以度数和一定是偶数,然后我们把所有的点分成两部分,一部分是度数为奇数的点,另一部分是读数为偶数的点,然后因为度数为偶数的点的度数和一定是偶数,所以我们有度数是奇数的点的度数和是偶数,所以度数是奇数的点有偶数个

证完了

这道题就做完了

二、联通

定义

一个块中所有的点可以互相到达,叫做联通块

一个图由一个联通块构成,叫做连通图

一个简单的证明

证明:一个 $ n $ 个点和至少 $ (n - 1)(n - 2) / 2 + 1 $ 条边的图是连通图

考虑一个简单的性质:一个 $ n - 1 $ 个点的图,至多有 $ (n - 1)(n - 2) / 2 $ 条边,这个可以利用组合数学去思考,也就是每条边和其他的 $ n - 1 $ 条边连接,就是 $ n(n - 1) $,然后每条边计算了两次,除以 $ 2 $ 即可,这里有 $ n - 1 $ 个点,计算方法一样

这 $ n - 1 $ 个点的图一定是联通的

那我们考虑 $ n $ 个点的图,$ (n - 1)(n - 2) / 2 + 1 $ 条边的图是连通图

这并不显然,所以我们考虑证明

如果分成两半,一半是 $ k $,一半是 $ n - k $,考虑最少得边,一定是 $ \frac{k(k - 1)}{2} + \frac{(n - k)(n - k - 1)}{2} + 1 $

证明这个比刚刚的大就可以了,如果比他大就可以证明 $ (n - 1)(n - 2) / 2 + 1 $ 是最小的,然后就得证了

拆开手算即可

另一个证明

一个图和它的补图至少有一个是联通图

考虑这么证明

假设有两个点集互不联通,然后这个图的补图就可以使得这两个点集互相联通,也就是这个图的补图中,其中一个点集要向另一个点集分别连边,这不就联通了吗?然后这就是一个连通图

否则的话原图是联通图

证完了

又是一个证明

房间里有 $ (m - 1)n + 1 $ 个人,证明要么存在 $ m $ 个人两两不认识,要么存在一个人至少认识 $ n $ 个人

我们考虑如果不满足其中一个条件,一定满足另一个条件

我们假设第二个条件不成立,那么一定存在 $ m $ 个人两两不认识

我们根据假设,可以得到,每个人最多认识 $ n - 1 $ 个人,然后我们考虑把其中一个点认识的人删掉,也就是说最多会删掉 $ n - 1 $ 个点,这件事我们干 $ m - 1 $ 次,就会剩下 $ m $ 个点,这 $ m $ 个点一定没有边

可以自己画画试试

如果不满足第二个条件不成立,那么第二个条件就成立,也就是说,第二个条件只有成立和不成立,而不成立的话另一个条件又一定成立,所以这个命题是成立的

再来一个证明

连通图 $ G $ 满足任意两条边至少有一个点是重合的,证明 $ G $ 要么是 $ 3 $ 个点的完全图,要么是星星(菊花图)

我们考虑这么证

首先菊花图的话要保证只有一个点度数大于等于 $ 2 $,而菊花图是满足条件的,和上面的题一样,我们考虑不是菊花图的情况

首先如果没有点度数大于等于 $ 2 $,也就是说只有一条边,有保证是连通图,所以这种简单的情况就是两个点一条边,不再考虑了

如果有两个点的度数大于等于 $ 2 $,那么我们就可以发现,这两个顶点分别引出一条边,就不满足至少一个点重合的条件,唯一的特例是三个点的完全图,可以自己想一下

然后就证完了

这是最后的证明了

证明一个连通图山区任意一个点连通性不变

考虑如果存在度数是 $ 1 $ 的点,删掉就可以了

如果不存在,找一个起点,删掉距离这个点最远的点就可以了

可以自己想一想正确性

三、欧拉路和欧拉回路相关证明

欧拉路:在一个图中,把所有边都正好走一次的路径

欧拉回路:起点终点同为一个点的欧拉路

欧拉回路的证明

考虑找一个点,这个点到出去转一大圈再回来,这样经过这个点连接的两条边,也就是说,这样构造左右的点的度数必须是偶数

然后就证出了欧拉路的构造,也就是说,只需要做一个 DFS 就可以了

欧拉路

可以存在两个度数是奇数的点,从一个点进入,从另一个点出去,这样可以和之前一样的方式构造出欧拉路

DFS 的时候要指定起点

四、哈密顿回路相关证明

经过所有点恰好一次的路径就叫做哈密顿回路

先看一个简单的证明

有 $ n + 1 $ 个不同的 $ \leq 2n $ 的正整数,有两个数互质

证明:很显然,$ n $ 和 $ n + 1 $ 是互质的,$ n + 1 $ 个数一定有两个数相连,然后就证完了

一个小定理的证明

$ n $ 个点,若每个点度数 $ \geq \frac{n}{2} $,一定存在哈密顿回路

反证,假设不存在哈密顿回路

那我们考虑原图是 $ G $,然后在原图中加边,得到一个 $ G' $,使得任意一条不在 $ G' $ 中的边 $ e $,有 $ G' + e $ 存在哈密顿回路,也就是说,加入所有不影响哈密顿回路存在的边

然后我们考虑那些不在 $ G' $ 中的边,比如说 $ e $,假设连接了 $ v_1 - v_n $,也就是说,在 $ G' $ 中一定有 $ v_1 - v_2 - v_3 - \dots - v_{n - 1} - v_n $

这里我们可以得到,如果 $ G' $ 中,$ v_1 $ 和 $ v_i $ 有边,那么 $ v_n $ 和 $ v_{i - 1} $ 一定没有边,不然存在哈密顿回路,可以自己画画试试

这样的 $ i $ 至少存在 $ \frac{n}{2} $ 个,因为 $ 1 $ 的度数至少是 $ \frac{n}{2} $,然后在 $ v_n,v_{i - 1} $ 中,这个 $ i - 1 $ 也至少有 $ \frac{n}{2} + 1 $ 个,所以 $ v_n $ 加上自己,有 $ \frac{n}{2} $ 个点不能连接,这样它的度数就 $ < \frac{n}{2} $,与已知条件不符

所以一定存在哈密顿回路

证完了

一个类似的证明

任意两个不相邻的点,度数和 $ \geq n $,那么这个图存在哈密顿回路

证明的方法差不多,其实可以想象把上一个证明的已知条件中每个点度数 $ \geq \frac{n}{2} $,把两个点的度数加起来,就 $ \geq n $,不相邻是因为有公共边

难度较大的证明

给定一个 $ 4 \times n $ 的棋盘,里面有一个马,证明没有经过所有点的回路,也就是哈密顿回路

首先自己模拟一下就知道没有,然后考虑证明

我们把所有点标成黑白的,很显然,一个点无论怎么跳,一定从黑跳到白,白跳到黑(一共 $ 8 $ 种跳法,自己画画),然后就可以发现想跳哈密顿回路,一定要跳奇数次,但是奇数次一定会跳到和起点不一样的颜色,所以跳不回起点

五、图的存储和遍历

邻接矩阵

一种简单的方法,我们使用邻接矩阵,用一个数组 bool A[][] 来存储,如果点 $ i $ 和 $ j $ 有边,那么 $ A_{i,j} = 1 $,否则是 $ 0 $

蛋这种方法在稀疏图里并不高效,$ O(n) $ 遍历,空间复杂度是 $ O(n^2) $

邻接表

这里使用 vector 去优化,我们把一个点连接的其他点放在一个 vector 里面,开 $ n $ 个 vector

代码:

vector < int > Edge[1005];

inline void add (int u, int v) {

    Edge[u].push_back (v);

    Edge[v].push_back (u);

}

inline void search (int u) {

    for (int i = 0; i < Edge[u].size (); ++ i) {

        int v = Edge[u][i];

    }

}

链式前向星

深受广大 OIer 喜爱的存储方式,这里利用链表代替 vector,全部放到了一个一维数组,只存边,空间复杂度很优,是 $ O(m) $ 的

int head[1005], next[1005], to[1005], cnt;

inline void add (int u, int v) {

    to[++ cnt] = v;

    next[cnt] = head[u];

    head[u] = cnt;

    to[++ cnt] = u;

    next[cnt] = head[v];

    head[v] = cnt;

}

inline void search (int u) {

    for (int i = head[u]; i; i = next[i]) {

        int v = to[i];

    }

}

CF1472C

这个题虽然不需要图论去做,但是因为这是图论课,所以我们需要使用图论(乐

首先我们考虑建图,一个点 $ i $,和点 $ i + a_i $ 连边,相同的,点 $ i + a_i $,就要和 $ i + a_i + a_{i + a_i} $,连边

然后遍历所有的链就可以得到答案

代码:

# include <bits/stdc++.h>

using namespace std;

inline void solve () ;

signed main () {

    int T = 1;

    cin >> T;

    while (T --) {

        solve ();

    }

    return 0;

}

int n, a[200005];

vector < int > Edge[200005];

int vis[200005];

inline int dfs (int u) {

    if (u > n || vis[u]) return 0;

    vis[u] = 1;

    if (Edge[u].size () == 1) return a[u] + dfs (Edge[u][0]);

    else return a[u];

}

inline void solve () {

    cin >> n;

    for (int i = 1; i <= n; ++ i) vis[i] = 0, Edge[i].clear ();

    for (int i = 1; i <= n; ++ i) cin >> a[i];

    for (int i = 1; i <= n; ++ i) Edge[i].push_back (i + a[i]);

    int ans = 0;

    for (int i = 1; i <= n; ++ i) ans = max (ans, dfs (i));

    cout << ans << endl;

    return ;

}

六、二分图

二分图就是左边一列点,右边一列点,然后有一些边连接它们

如果左边选一个点,右边选一个点,保证一个点只选一次,并且左右的点有连边,就是一个正确的匹配

二分图最大匹配:最多的匹边变数量

归纳法证明简单例子

$ 1^2 + 2^2 + \dots + n^2 = \frac{n(n + 1)(2n + 1)}{6} $

首先,我们考虑当 $ n = 1 $。等式成立

然后我们考虑,如果 $ 1^2 + 2^2 + \dots + n^2 = \frac{n(n + 1)(2n + 1)}{6} $ 成立,能否推出 $ 1^2 + 2^2 + \dots + (n + 1)^2 = \frac{(n + 1)(n + 2)(2n + 3)}{6} $

多项式简单算算就可以了

然后我们考虑从 $ n = 1 $ 推到 $ n $,就可以推出来

这就是归纳法

一个定理的证明

在左边一列点集 $ A $ 中,找一个点集 $ S $,把 $ S $ 里面的点可以到达的右边一列点集 $ B $ 中的点组成的点集为 $ T $

如果对于任意的 $ S $,以及 $ T $,都满足 $ | T | \geq | S | $,一定存在一个完全匹配

如果集合 $ A,B $ 大小为 $ 1 $,定理一定满足

如果 $ A,B $ 大小是 $ n $,并且定理满足,考虑怎么推出 $ n + 1 $ 时满足

咕咕

匈牙利算法

用于求二分图最大匹配

这里相当于一个 DFS 的过程,尝试每一种选择,如果不行就回溯

我们可以考虑枚举左边一列的点,然后尝试第一种选择,往下走,选过的右边的点就标记,如果没有可以选择的,就看看有没有备选过的点,然后尝试修改之前的选择,取一个当前的的最大值

然后代码如下:

# include <bits/stdc++.h>

# define int long long

using namespace std;

int n, m, e, match[10005];

bool vis[10005];

vector < int > Edge[10005];

inline bool dfs (int u) {

    if (vis[u]) return 0;

    vis[u] = 1;

        for (int i = 0; i < Edge[u].size (); ++ i) {

        int v = Edge[u][i];

        if (! match[v] || dfs (match[v])) {

            match[u] = v;

            match[v] = u;

            return 1;

        }

    }

    return 0;

}

signed main () {

    cin >> n >> m >> e;

    for (int i = 1; i <= e; ++ i) {

        int u, v; cin >> u >> v;

        Edge[u].push_back (v + n);

        Edge[v + n].push_back (u);

    }

    int ans = 0;

    for (int i = 1; i <= n; ++ i) {

        memset (vis, 0, sizeof (vis));

        if (dfs (i)) {

            ++ ans;

        }

    }

    cout << ans << endl;

    return 0;

}

CF1764C

这道题非常简单,我们可以考虑以下规则:

如果 $ i $ 连向 $ j $,那么 $ j $ 就不能连向更大的点,同样,$ i $ 也不能连向更小的点,所以我们可以考虑 $ 1 $ 到 $ i $ 和 $ j $ 到 $ n $ 分别连编,这样可以使得答案最优,这样我们可以利用乘法原理来求得最大的答案

要求保证 $ a_i \neq a_{i + 1} $

这里我们用 $ T_i $ 记录连续的 $ a_i $ 相等的数有几个

然后每次枚举的时候加上 $ T_i $ 就可以避免重复,计算答案也方便了许多

代码如下:

# include <bits/stdc++.h>

# define int long long

using namespace std;

int t;

int n, a[200005];

int cnt, T[200005];

signed main () {

    cin >> t;

    while (t --) {

        cin >> n;

        for (int i = 1; i <= n; ++ i) cin >> a[i];

        sort (a + 1, a + 1 + n);

        int ans = -1;

        cnt = 0;

        memset (T, 0, sizeof (T));

        for (int i = 1; i <= n; ++ i) {

            if (a[i] != a[i - 1]) ++ cnt;

            ++ T[cnt];

            ans = max (ans, T[cnt] / 2);

        }

        int p = 0, q = n;

        for (int i = 1; i <= cnt; ++ i) {

            p += T[i], q -= T[i]; // q = n - p;

            ans = max (ans, p * q);

        }

        cout << ans << endl;

    }

    return 0;

}

七、树

边数等于点数 $ - 1 $ 的连通图叫做树

一个性质:任意两点的路径唯一

另一个性质:一个树一定存在度数为 $ 1 $ 的点,叫做叶子节点

证明很好证,反证,如果不存在,也就是说,度数至少为 $ 2 $,那么我们可以考虑总度数是 $ 2n $,边数至少为 $ n $,矛盾了

所以存在

存储

和图一样

遍历

这里使用 DFS 遍历

代码:

vector < int > Edge[10005];

inline void dfs (int u, int fa) {

    for (int i = 0; i < Edge[u].size (); ++ i) {

        int v = Edge[u][i];

        if (v == fa) continue;

        dfs (v, u);

    }

}

CF522A

把字符串当成节点建一颗树,然后遍历找深度最大的点就可以了

CF115A

咕咕

树的直径

树的直径就是树上的最长路径

这里有一个很简单的做法

从任意一个点 DFS,找一个最远的点,然后再找到这个点再做一次 DFS,找最远的点,这两个点连起来就是直径

代码如下:

树的重心

树的重心就是找一个点,删去以后使得剩下的 $ k $ 个连通分量点数最多的最少

这个比较简单,枚举点就可以了,可以做一遍 dfs 来解决

代码如下:

咕咕

CF690C2

咕咕

LCA 最近公共祖先

posted @ 2023-05-27 19:00  __Tzf  阅读(49)  评论(0)    收藏  举报