一个图是指资料
为了避免记号混淆,我们总是假定
称
顶点集是
我们常把一个图画成图形:为每个顶点画一个点,若两个顶点构成边则画一条线连接对应的两点。点和线的形状无关紧要,只要能表现出哪些顶点对构成边,哪些顶点对不构成边。
画图工具:graph editor
设
设
设
称两两不邻接的一族边或点独立。
若图
设
则称
若
则称
设
设
设

设
对于
设

设
证明下述结果。设
作为推论,任一图里度是奇数的顶点总有偶数个。
连通的3-正则图是什么样的?注意到 3-正则图必有偶数个顶点。
给你一个有
把图上的每个顶点涂成红色或蓝色,有
设红红边有
条件变成:选择
只要这
const int mod = 998244353; //是素数
int inverse(long long x) { // 模p下的逆元
if (x == 1) return 1;
return mod - mod / x * inverse(mod % x) % mod;
}
const int maxn = 2e5 + 5;
int deg[maxn]; // 度
long long f[maxn]; // 阶乘
long long choose(int n, int m) { // 组合数
if (m < 0 || m > n) return 0;
return
f[n] * inverse(f[m] * f[n - m] % mod) % mod;
}
int main() {
int n, m, k;
cin >> n >> m >> k;
while (m--) {
int u, v;
cin >> u >> v;
deg[u]++;
deg[v]++;
}
int odd = 0;
for (int i = 1; i <= n; i++)
if (deg[i] % 2)
odd++;
f[0] = 1;
for (int i = 1; i <= n; i++)
f[i] = f[i - 1] * i % mod;
long long ans = 0;
for (int i = 0; i <= min(odd, k); i += 2)
ans += choose(odd, i) * choose(n - odd, k - i) % mod;
cout << ans % mod << '\n';
}
非空图
其中诸

图
我们常把路径上的点依其自然顺序写出,记如
对于
我们用类似的直观记号表示路径的串接;例如,若三条路径的并

给定顶点集
给定图
称两条或多条路径独立,若其中每一条路径都不包含另一条路径的内部顶点。例如,两条
设
图
若一条边连接环上两点但它本身不是环的边,则称之为环的弦。若

图
设图
证明 根据定义,显然有
命题
证明 设
图
若途径里的顶点相异,它就成为路径。
注:有的书上把这里定义的途径称为路径,而把我们前面定义的路径称为简单路径。
若图
设

此图有三个连通块,加粗的部分是每个连通块里的一个极小连通生成子图。
称图
每个非空图都是
使得
换言之,删除
若
换言之,删除
为了今后表述方便,我们采用一个术语:分离。
设
若
若图
证明:
先设
再设每个顶点都与
设

此图有割点

const int maxn = 1e5 + 5;
const int maxm = 2e5 + 5;
struct Edge {
int to;
int next;
};
Edge edge[maxm];
int head[maxn];
int e_cnt;
void init() {
memset(head, -1, sizeof head);
e_cnt = 0;
}
void add(int from, int to) {
edge[e_cnt] = {to, head[from]};
head[from] = e_cnt++;
}

const int maxn = 1e5 + 5;
vector<int> g[maxn];
void add(int from, int to) {
g[from].push_back(to);
g[to].push_back(from);
}
//对于边uv,add(u, v) 和 add(v, u)
树和森林
二分图
欧拉图
无环图,即不含有任何环的图,也称为森林。连通的森林称为树。树里度是

不难验证,每个非平凡的树都有叶子。从树里删除一个叶子,剩下的图仍是树。
对于图
设
每个连通图都含有一个生成树。每个非空图都含有一个生成森林。

有时宜将树里的某个顶点特别看待,这样的顶点称为树的根。树
有根树上有一个自然的偏序:
在右图中,
可通过下述过程生成的树称为雪花树:
右图是
给你一个有
我们把上述第一个顶点称为雪花树的中心。雪花树在以中心为根时,根有
若在树
int main() {
int n; cin >> n;
vector<vector<int>> g(n + 1);
for (int i = 0; i < n - 1; i++) {
int u, v; cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
int ans = 0;
for (int i = 1; i <= n; i++) {
vector<int> deg;
for (int j : g[i])
if (g[j].size() > 1)
deg.push_back(g[j].size());
sort(deg.rbegin(), deg.rend());//从大到小排序
for (int j = 0; j < deg.size(); j++)
ans = max(ans, (j + 1) * deg[j] + 1);
}
cout << n - ans << '\n';
}
int main() {
int n; cin >> n;
vector<vector<int>> g(n + 1);
for (int i = 0; i < n - 1; i++) {
int u, v; cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
auto cmp = [&](int x, int y) { // lambda 表达式
return g[x].size() > g[y].size();
};
int ans = 0;
for (int i = 1; i <= n; i++) {
sort(g[i].begin(), g[i].end(), cmp);
for (int j = 0; j < g[i].size() && g[g[i][j]].size() > 1; j++)
ans = max(ans, (j + 1) * (int) g[g[i][j]].size() + 1);
}
cout << n - ans << '\n';
}
设
若一个

二分图
二分图不含有奇环(为什么?)。实际上,此性质完全刻画了二分图。
命题

图
编号为
若
vector<int> g[300005];
int f[3005][3005];
int main() {
int S, T, M;
cin >> S >> T >> M;
while (M--) {
int u, v;
cin >> u >> v;
g[u].push_back(v - S);
}
for (int a = 1; a <= S; a++) {
for (int i = 0; i < g[a].size(); i++)
for (int j = 0; j < i; j++) {
int c = min(g[a][i], g[a][j]);
int d = max(g[a][i], g[a][j]);
if (f[c][d] == 0) {
f[c][d] = a;
} else {
cout << f[c][d] << ' ' << a << ' ' << c + S << ' ' << d + S;
return 0;
}
}
}
cout << -1;
}
→
→ 
如果图上的一个封闭途径走过每条边恰好一次,那么称之为一个欧拉环游或欧拉回路。若在一个图上可进行欧拉环游,则称之为欧拉的,也称这样的图为欧拉图。
定理 一个连通图是欧拉的当且仅当它的每个点的度都是偶数。
有向图
重图
带权图
一个有向图是指资料
映射
一个有向图上可能有多条从顶点
若

容许图里有重边或自环就得到重图的概念。形式地说,一个重图是指一对不相交的集合
图就是没有重边和自环的重图。前面介绍的适用于图的术语几乎都可以用于重图,不过也有些许差别:
在 OI 里,我们不使用重图这个术语,而是把图和重图统称为无向图;为了强调一个无向图(或有向图)没有重边和自环,我们说它是简单无向图(或有向图)。
一个有向图或无向图
对于带权图,我们定义路径的权值是路径上边的权值之和。
每个顶点的出度(或入度)都是
有一个有
找出一个有向环。
每个点的出度都是
int main() {
int n; cin >> n;
vector<int> a(n + 1);
for (int i = 1; i <= n; i++)
cin >> a[i];
vector<bool> vis(n + 1);
int v = 1;
while (!vis[v]) {
vis[v] = true;
v = a[v];
}
vector<int> cycle;
int u = v;
do {
cycle.push_back(u);
u = a[u];
} while (u != v);
cout << cycle.size() << '\n';
for (int x : cycle)
cout << x << ' ';
cout << '\n';
}
不含环的有向图称为有向无环图,简称 dag。
在 dag 的顶点集上有一个自然的偏序:
把顶点排列成符合此偏序的操作称为拓扑排序。
对给定的 dag,重复下述过程:
const int maxn = 1e5 + 5;
vector<int> g[maxn];
vector<int> toposort(int n) {
vector<int> deg(n + 1);
for (int i = 1; i <= n; i++)
for (int j : g[i])
deg[j]++;
vector<int> p;
for (int i = 1; i <= n; i++)
if (deg[i] == 0)
p.push_back(i);
for (int i = 0; i < p.size(); i++)
for (int j : g[p[i]])
if (--deg[j] == 0)
p.push_back(j);
if (p.size() != n) //不是dag
return {};
return p;
}
有
每个关系相当于一条有向边。依次读入每个关系,往图里加一条边,做一次拓扑排序;若发现矛盾就结束,若拓扑序不唯一,即某时刻有不止一个点入度为零,就做个标记。
vector<int> g[26];
int n, m;
vector<int> toposort() {
vector<int> indeg(n);
for (int i = 0; i < n; i++)
for (int j : g[i])
indeg[j]++;
vector<int> p;
for (int i = 0; i < n; i++)
if (indeg[i] == 0)
p.push_back(i);
bool ok = true;
for (int i = 0; i < p.size(); i++) {
if (p.size() != i + 1)
ok = false;
for (int j : g[p[i]])
if (--indeg[j] == 0)
p.push_back(j);
}
if (p.size() != n) //有环
return {};
if (!ok) //拓扑序不唯一
p.push_back(-1);
return p;
}
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
char a, b, c;
cin >> a >> c >> b;
g[a - 'A'].push_back(b - 'A');
vector<int> p = toposort();
if (p.empty()) {
cout << "Inconsistency found after " << i << " relations.\n";
return 0;
}
if (p.back() != -1) {
cout << "Sorted sequence determined after " << i << " relations: ";
for (int i = 0; i < n; i++)
cout << (char) (p[i] + 'A');
cout << ".\n";
return 0;
}
}
cout << "Sorted sequence cannot be determined.\n";
}
给你一个
求满足下列条件的有向无环图的数量,模
限制:
输入
3
1 3 2
输出
4

考虑递推。从序列
以
const int mod = 998244353;
//快速幂
long long power(long long x, long long n) {
int ans = 1;
while (n) {
if (n & 1)
ans = ans * x % mod;
x = x * x % mod;
n >>= 1;
}
return ans;
}
int main() {
int n; cin >> n;
vector<int> p(n);
for (int i = 0; i < n; i++)
cin >> p[i];
vector<int> a;
long long prod = 1;
long long non_free = 0;
for (int i = n - 1; i >= 0; i--) {
while (!a.empty() && p[i] > p[a.back()]) {
prod = prod * (power(2, a.back() - i) - 1) % mod;
non_free += a.back() - i;
a.pop_back();
}
a.push_back(i);
}
long long tot = (long long) n * (n - 1) / 2;
prod = prod * power(2, tot - non_free) % mod;
cout << prod << '\n';
}
一般我们不区别同构的图。例如,我们通常写 $G=G'$ 而非 $G\iso G'$,当我们谈到 $17$ 顶点的完全图,不用管这些顶点是什么。---
Note: 围长(girth)和周长(circumference)都有“一圈有多长”的意思。 girth 指物体有多粗,例如树干,人的腰围等。circumference 指一个物体在平面上的边界的长度,例如圆的周长,国家的边界线长度,城的周长等
Note:比路径更宽松的概念