NOI 知识一览-2. 图论
2. 图论
本文所讨论的图一般为简单图,且一般认为
2.1(a) 二分图
A. 二分图的概念与判定
联赛内容,懒得写了。
B. 匈牙利算法
用于求解二分图最大匹配问题。
对于图
匈牙利算法基于的一个重要结论是:
- 一组匹配是最大匹配,当且仅当此时不存在增广路。
注意到若存在一条增广路,则容易构造一个更大的匹配。由此只需证明若当前匹配非最大,则一定存在增广路。
考虑找到当前匹配与最大匹配的边集的对称差,图中每个点度数不超过
实现时,我们只需依次从每个点出发尝试搜出一条增广路。由于已经被选入匹配中的点不会再被移出匹配,因此可以基于贪心证明其正确性,时间复杂度
vector<int> g[N]; bool vis[N];
bool dfs(int x) {
if(vis[x]) return false;
vis[x] = true;
for(int y : g[x]) if(a[y] == 0 || dfs(a[y]))
return a[y] = x, true;
return false;
}
for(int i = 1; i <= n; i++) cl(vis, 0), dfs(i);
事实上,可以证明若将匹配的左部点记为
1 ,未匹配的左部点记为0 ,则按照枚举顺序拼接左部点的匹配情况,匈牙利算法求出的最大匹配是字典序最大的。
C. KM 算法
用于求解二分图最大权完美匹配问题。
相关问题:
最大权匹配可以简单转化到此问题,有负权的情况也是平凡的。
最大权最大匹配还是直接流吧。
一个相对高等的视角是考虑用线性规划表达之:
其中
也就是说我们需要给每个点赋值一个顶标
我们不考虑直接求出
考虑沿用匈牙利算法的思路,每次从一个点
-
我们跑出
rt 的交错树,设左右部点分别为S,T ,补集分别为S^\prime,T^\prime ; -
求出
V = \min_{x\in S,y\in T'}\{a_x+a_y-w(x,y) \} ,然后把S 中的顶标-V ,T 中的+V ; -
这样一定可以在交错树中至少加入一个右部点(注意有此右部点已经匹配的情况)。
可以证明若存在增广路,最多重复上面的过程
n 次后一定可以找到一条。 -
上面的过程的复杂度是
\mathcal O(n^4) 的,瓶颈在于求V 。 -
为了优化,我们考虑维护
\min_{x\in S,y\in T'}\{a_x+a_y-w(x,y) \} 的值以降低复杂度。具体地我们对于每个
y\in T^\prime 维护slack(y)=\min_{x\in S}\{a_x+a_y-w(x,y) \} 的值,这样可以把单次的时间复杂度降为\mathcal O(n) ,总复杂度\mathcal O(n^3) 。使用 BFS 也可以得到相同的复杂度,不再赘述。 -
实现时我们把每个非
rt 的左部点用其匹配中右部点的另一方表示;对每个右部点记录pre 表示其在交错树中的二级祖先。
int pre[N]; bool vis[N]; ll sl[N];
void KM() {
fo(i, 1, n) {
A[i] = B[i] = 0;
fo(j, 1, n) A[i] = max(A[i], w[i][j]);
}
fo(rt, 1, n) {
int x = rt, cur = 0; match[0] = x;
cl(vis, 0), cl(sl, 0x3f), cl(pre, 0);
fo(rd, 1, n) {
int p = -1; ll V = INF;
fo(i, 1, n) if(!vis[i]) {
ll val = A[x] + B[i] - w[x][i];
if(val < sl[i]) sl[i] = val, pre[i] = cur;
if(sl[i] < V) V = sl[i], p = i;
}
A[rt] -= V;
fo(i, 1, n) {
if(vis[i]) B[i] += V, A[match[i]] -= V;
else sl[i] -= V;
}
vis[p] = true, x = match[p], cur = p;
if(x == 0) break;
}
while(cur)
match[cur] = match[pre[cur]], cur = pre[cur];
}
}
-
KM 算法和匈牙利算法都可以带修,只需判断改动的边是否是匹配边,若是则删去之。
然后重新从这个点出发跑一遍算法过程即可。单次修改的复杂度是
\mathcal O(n^2) 的。
D. Hall 定理
E. 二分图常用结论
2.1(b) 网络流算法
A. 最大流
B. 最小割
可行边与必须边
在找到任意一组最大流方案后,可以得到两个经典结论:
- 边
(u,v) 是最小割的可行边,当且仅当其满流且残量网络上不存在路径u\to v 。 - 边
(u,v) 是最小割的必须边,当且仅当其满流且残量网络上存在路径S\to u 和v\to T 。
容易证明只有满流边才可能被割去。
证明上述结论可以转用最大流视角考虑,前者等价于该边流量减小
1 后全图最大流减小1 ,后者等价于流量增大1 后最大流增大1 。这样,二者的条件都是显然的。
特化到二分图匹配问题后,有如下结论:
- 边
(u,v) 是最大匹配的可行边,当且仅当其流为1 或残量网络上(u,v) 在同一个 SCC 中。 -
边
(u,v) 是最大匹配的必须边,当且仅当其流为1 且残量网络上(u,v) 不在同一个 SCC 中。考虑增广路的若干性质,上述结论是易证的。
C. 费用流
D. 上下界网络流
E. 最小割树
最小割树的概念
最小割树旨在解决如下问题:
给定一张无向连通图
G ,求任意两点间最小割。
此算法(称为 Gomory-Hu 算法)可以给出一棵带边权的树
进一步地,此树可以保证对于
-
另一个需要区分的概念是“等价流树”,使用 Gusfield 算法构建。
其只满足以上的第一条性质,不能构造方案而只能求任意两点间最小割,不过实际使用起来没有明显区别。
推理性质
下记
- 引理:对于任意三个点
a,b,c ,有L(a,b)\ge \min(L(a,c),L(b,c)) 。
证明可以考虑转为最大流然后讨论。
立刻得到:
- 最小割树的判定:若一棵树满足:对于树上任意一条边
(u,v,w) ,有L(u,v)=w 且将该边删去后树上两个连通块构成了一组(u,v) -最小割的方案,那么这棵树满足对于任意两点(u,v) 有两点间最小割等于其在树上路径中的最小值,称为最小割树。
证明可以考虑取最小值边
(s,t) ,由引理L(u,v)\ge L(s,t) ,而取(s,t) -最小割即可构造证明L(u,v)\le L(s,t) 。
现在我们只需要构造一棵树使得上面的条件被满足。事实上我们可以证明:
-
分治的合理性:对任意
(s,t) -最小割(S,S') ,对任意点对u,v\in S 都存在(u,v) -最小割(T,T') ,使得T\sube S 。务必这里只对
u,v\in S 有效!
证明可以考虑分类调整。以下我们记
\delta(P)=(P,\overline{P}) 为一组割,一些地方会混用此记号表示此割的大小。不妨设
u\in S ,若t\not\in T ,那么我们取\delta(S\cap T) 就是一组(u,v) 割,可以证明其等于\delta(T) 。
- 这是因为
\delta(S)+\delta(T)\ge \delta(S\cup T)+\delta(S\cap T) ,其分别是(s,t) 和(u,v) 的两组割。由定义两组最小割的大小对应相等。若
t\in T ,构造\delta(S\backslash T) 即可,详细证明不再展开。
Gomory-Hu 算法
因此,我们可以每次随机选择图上的两个点,用 Dinic 跑出二者间最小割的一个方案
然后把图
每次递归时记录当前图对应原图上的哪些点,当此点集大小为
然后我们添加一条边把两边的树连上,再把新加进去的两个虚点从划分的方案中删掉,返回即可。
可以归纳证明此算法的正确性:只需证明新加的那条边合法即可。
细节不再展开,因为场上我们大概率不会直接实现此算法。
容易证明,对于任意图,Gomory-Hu 算法会运行恰好
近年来有一些算法可以对此问题做到
Gusfield 算法
Gusfield 算法较于 Gomory-Hu 算法简单许多。
它不需要利用缩点 / 划分等一系列复杂的操作,只需要在每次跑出
不过代价是其只能求出等价流树,无法构造方案。
vector<pair<int, int> > g[N]; bool vis[N];
set<int> MinCut(int u, int v) {
memcpy(edge, Edge, sizeof(edge));
int fl = 0; S = u, T = v;
while(bfs()) fl += dinic(S, INF);
set<int> res; queue<int> Q;
Q.push(u), cl(vis, 0), vis[u] = true;
while(Q.size()) {
int x = Q.front(); Q.pop();
for(int i = head[x]; i; i = Next[i]) {
int y = ver[i];
if(edge[i] && !vis[y]) vis[y] = true, Q.push(y);
}
}
g[u].push_back(make_pair(v, fl)), g[v].push_back(make_pair(u, fl));
fo(i, 0, n) if(vis[i]) res.insert(i);
return res;
}
void build(vector<int> vec) {
if(vec.size() == 1) return ;
int u = vec[0], v = vec[1];
auto now = MinCut(u, v); vector<int> L, R;
for(int x : vec) {
if(now.find(x) == now.end()) R.push_back(x);
else L.push_back(x);
}
build(L), build(R);
}
Record.
F. 全局最小割
2.2 图论建模
咕咕咕。
2.3 图论杂项
A. Menger 定理
- 非平凡图
G 是k -点连通图的充要条件是:对于任意两个不同的点u,v\in G ,都存在k 条点不相交的从u 到v 的路径。 - 非平凡图
G 是k -边连通图的充要条件是:对于任意两个不同的点u,v\in G ,都存在k 条边不相交的从u 到v 的路径。
它是最大流 - 最小割定理的特例。
利用 Menger 定理可以对
-
对点双中任意点
x 和任意边e ,存在同时经过二者的简单环;(证明:在
e 中间拆出一个点y ,使用 Menger 定理即可) -
对点双中任意不同两点
x,y 和任意边e ,存在路径x\rightsquigarrow e\rightsquigarrow y ;(证明:取含
x,e 的环,讨论y\rightsquigarrow x 与环的首个交点即可) -
对点双中任意不同三点
x,y,z ,存在路径x\rightsquigarrow y\rightsquigarrow z ;(证明:把
z 裂出一条边即可)
它们可以用于推理图性质从而计数。注意点双的性质比边双强,一般优先从点双下手。
B. 欧拉图
判定
- 无向连通图
G 是欧拉图当且仅当每个点的度数都是偶数。 - 无向连通图
G 是半欧拉图当且仅当有且仅有两个点的度数是奇数。 - 有向弱连通图
G 是欧拉图当且仅当每个点的出入度相等。 - 有向弱连通图
G 是半欧拉图当且仅当有一个点的出度比入度大1 ,另有一个点的出度比入度小1 ,其余每个点的出入度相等。
在上述命题的证明中,必要性是显然的,充分性的构造可以考虑通过不断地寻找图中的环并将他们相连接得到。
构造
一般采用 Hierholzer 算法。
其核心是不断往当前回路的某个点中递归地插入环,这体现在 DFS 过程的实现中等价于在回溯的同时找别的环,也即一路 DFS 下去,然后倒序加入搜过的边。
可以证明上述算法的正确性,一个详细解释可以见 AlexWei 的博客。
事实上,只要保证搜索的起点和访问边的顺序,这种求法求出的路径满足字典序最小,因为每个点的后继都取到了理论最小值。
void dfs(int x) {
for (int y = 1; y <= n; y++)
if (g[x][y] >= 1) {
g[x][y]--, g[y][x]--;
dfs(y);
}
ans[++cnt] = x;
}
dfs(1), reverse(ans + 1, ans + cnt + 1);
混合图的构造需要利用网络流确定出入度。
-
另一种构造算法称为 Fleury 算法,核心思路是避免走桥:我们每次走到一个点都尽可能选择一条不是桥的出边走掉。
复杂度较劣,几乎没有实践意义。
计数
有向欧拉图的欧拉回路是可以计数的,这称为 BEST 定理。
对于任意有向欧拉图
对于每个点
具体地,我们从
由于每个点出入度相同,这样走一定不会卡在某个点走不了了,也就是说一定可以走会到
现在再证明每条欧拉回路唯一对应一种枚举方案:我们取出
综上,有向欧拉图的欧拉回路数为:
其中
- 有向半欧拉图的欧拉路计数可以通过加一条辅助边简单转化为欧拉回路,因此是简单的。
- 而无向图欧拉回路计数是 #P 完全的,证明可见这里。
C. 竞赛图
竞赛图与哈密顿路
-
竞赛图缩点后是一条链。更严格地说,它是一张完全 DAG,但是我们可以按照偏序关系将其直接看成链。(容易反证)
下面的大部分性质都将围绕此性质展开。
-
若一个竞赛图中存在环,则该图中一定存在三元环。
-
竞赛图一定存在哈密顿路。
证明:归纳地证明之,当我们在图中加入
- 若有边
n\to 1 或(n-1)\to n ,则直接连上了; - 否则反证可知一定存在一个
p 使得p\to n,n\to (p+1) ,接到这里即可。
- 强连通竞赛图一定存在哈密顿回路。
证明:
找到任意一条哈密顿路重标号为
接下来我们不断地找到第一个点
然后我们设
这样我们使得环扩大了一些。不断重复这个过程直至
由此,我们知道任意竞赛图都可以被看作若干哈密顿回路连接而成的链,不妨称此为“竞赛图的 SCC 链结构”。
兰道定理
十分强大的定理。
定义竞赛图
此外,竞赛图中的若干 SCC 中的点所队应的出度一定依次排列(即链上倒数第
证明:
原定理的必要性是显然的,对于前
充分性可以构造证明:先构造竞赛图,对所有
我们找到首个
对于下面的推论,“依次排列”的性质是显然的,第二点同样考虑前
-
兰道定理的一个重要作用就是帮我们成功地在代数意义上刻画了 SCC 的形态,
这使得我们可以通过更优秀的复杂度找到所有的 SCC 或维护 SCC 个数。
-
此外,其充分性证明使用的构造也使得我们可以通过度数去构造一个竞赛图。
一道值得提到的例题是 CF1268D:
给定一张竞赛图,定义一次操作为选取一个顶点并翻转其所有邻边的方向。
求出是否存在一个操作序列使得图强连通,若存在则求出最小操作次数,以及操作次数最小的操作方案数,对
998244353 取模。
依赖于一些结论,本题最后的部分是平凡的:
-
任意
n(\ge 4) 阶强连通竞赛图存在n-1 阶强连通竞赛子图。证明可以通过去掉
n 号点后讨论\{1,2,\cdots,n-1\} 的导出子图的 SCC 链结构得到。由此,任意
n(\ge 4) 阶强连通竞赛图一定可以翻转其中一个点使得图依然是强连通的。 -
证明:对 SCC 链长讨论,$k=1/k\ge 3$ 可以直接构造,否则一定存在一个 SCC 大小至少为 $4$,由上一个引理显然。
因此对
D. 弦图
远古科技。
概念与基本性质
弦图是一种特殊的图,很多在一般图上的 NP-Hard 问题在弦图上都有优秀的线性时间复杂度算法。
-
弦:连接环中不相邻两点的边。
-
弦图:任意长度大于
3 的环都有一个弦的图称为弦图。 -
性质:弦图的任意导出子图一定是弦图。
这是因为导出子图上存在的无弦环,原图上也存在。
极小点割集与单纯点
-
弦图上极小点割集一定是团:
对于弦图上任意两点
x,y ,若S 是一个x,y 的极小点割集(删去S 中的点后二者不连通),那么S 的导出子图一定是团。
证明:
考虑删去
S 后的图含x,y 的连通块分别为A,B ,那么若(u,v) 间无边,那么考虑u,v 在A,B 中的邻居u_A,u_B,v_A,v_B ,容易证明它们一定存在。再考虑u_A,v_A 二者间的最短路和u_B,v_B 二者间的最短路构成环,简单讨论四个点相等的情况可以通过弦图的定义推理出(u,v) 有边。
-
单纯点的定义:若
x 满足N(x)\cup \{x\} 的导出子图是团,则称x 为单纯点。(即不存在(x,u),(x,v) 有边而(u,v) 无边) -
任何一个弦图至少存在一个单纯点;非完全图的弦图至少存在两个不相邻的单纯点。
证明:考虑从
n\ge 4 处开始归纳。对完全图,第一条显然成立,否则取
(u,v) 不相邻,找出任一u,v 的极小点割集I ,设去掉I 后u,v 所在的连通块分别为A,B 。由归纳假设,
A\cup I 的导出子图若不是完全图则一定有两个不相邻的单纯点,其中必定有一个在A 中;若是完全图则A 中任意点都是单纯点。同理B 中一定也有一个单纯点,由于A,B 间无边,可以直接取这两个单纯点完成构造。
完美消除序列
我们定义点集的排列
容易发现,一个弦图一定存在完美消除序列:每次取出任意单纯点即可构造。
此外,存在完美消除序列的图一定是弦图:每个环在序列中第一次出现的点一定与其环上相邻点有边。根据完美消除序列的定义,这两个相邻点一定有边。
至此我们得到"图存在完美消除序列"和"图是弦图"两条件是等价的。
MCS 算法
中文名最大势算法(Maximum Cardinality Search),可以在
- 每次找到一个未被标记的
x 使得与其相邻的已标记点数l_i 最大; - 标记
x ,然后更新别的点的所有l_x ; - 重复以上过程直至所有点都被标记,此时按照把所有点按照其被标记的顺序从后往前(也即倒序)形成一个序列。
- 实现时维护
l_x 最大的位置可以使用桶配合链表做到线性,若偷懒使用set会多一个\log 。
然后判定找到的
-
倒序遍历所有
p_i ,设p_{i\cdots n} 中与p_i 相连的点依次为p_{c_1},p_{c_2},\cdots,p_{c_k} ,我们判断所有p_{c_i}(i>1) 是否与p_{c_1} 相连即可。这是因为若上述条件成立,那么''
p_{c_i},p_{c_j}(i,j>1) 间有边"的条件已经在p_{c_1} 处判定,自然也成立。 -
判断部分具体实现时可以做到
\mathcal O(n^2+m) 或\mathcal O((n+m)\log n) ,也可以使用哈希表做到\mathcal O(n+m) 。
下面给出 SP5446 代码。
const int N = 1e6 + 10;
int n, m; vector<int> g[N];
int val[N], p[N], w[N]; list<int> a[N];
list<int>::iterator pos[N]; unordered_set<int> G[N];
void Main() {
fo(i, 0, n) {
val[i] = p[i] = w[i] = 0;
g[i].clear(), G[i].clear(), a[i].clear();
}
fo(i, 1, m) {
int u, v; scanf("%d%d", &u, &v);
g[u].push_back(v), g[v].push_back(u);
G[u].insert(v), G[v].insert(u);
}
fo(i, 1, n) pos[i] = a[0].insert(a[0].end(), i);
int id = 0;
fr(i, n, 1) {
id++; while(a[id].empty()) id--;
int x = a[id].back();
a[id].pop_back(), p[i] = x, w[x] = i;
for(int y : g[x]) if(!w[y]) {
a[val[y]].erase(pos[y]), val[y]++;
pos[y] = a[val[y]].insert(a[val[y]].end(), y);
}
}
fo(x, 1, n) {
pair<int, int> c = make_pair(n + 1, -1);
for(int y : g[x]) if(w[y] > w[x])
c = min(c, make_pair(w[y], y));
if(c.second == -1) continue;
int u = c.second;
for(int y : g[x]) if(w[y] > w[x] && u != y)
if(G[u].find(y) == G[u].end())
return puts("Imperfect"), void();
}
puts("Perfect");
}
下证上述算法对任一弦图求出的序列一定是完美消除序列。
设
- 引理一:若存在
w(x)<w(y)<w(z) 使得存在边(x,z) 且不存在边(y,z) ,那么一定存在w(y)<w(p) 满足存在边(y,p) 且不存在边(x,p) 。
这可以简单反证证明:若不存在这样一个
这样我们考虑若
-
若
w(p)<w(z) ,那么由引理一又可以找到p^\prime 在更后面满足之前p 的性质。根据
(y,p^\prime) 间是否有边,无论如何都可以找到z\to x\to y\to p\to p' 或z\to x\to y\to p' 一条长度至少为4 的链满足若(z,p') 间有边就会构成无弦环。这样我们用新的p' 回过头继续上面的讨论。 -
若
w(p)>w(z) ,则可以用w(y)<w(z)<w(p) 在引理一中找到一个新的z^\prime 。之后与上一种情况类似,我们用新的
z' 回过头与p 继续上面的讨论。
不难发现,无论情况如何,我们都可以延长当前从
若需要更加形式化,可以考虑把这个引理写为如下形式:
- 引理二:任意弦图一定不存在
v_{0\cdots k}(k\ge 2) 满足: -
证明方法与上面类似。(之后的步骤是平凡的。)
应用
最大团与点色数
考虑任意一个极大团在完美消除序列中的第一个点
此外,容易证明任意图的点色数不小于最大团大小,而在弦图中我们只需考虑按“完美消除序列的倒序”贪心染色,每次取它的所有邻居的 mex 即可。由于色数是
最大独立集与最小团覆盖
最大独立集:正序按照弦图的完美消除序列遍历,贪心选择没有与已选的点有直接连边的点。
证明:显然这是字典序最小的方案。
我们找到第一个点
x 满足我们的贪心中选了它而正解中没有选x ,分类讨论:
- 若正解中没有选
x 的任一邻居,那么把x 加入答案依然合法;- 否则将
x 的某个邻居调整到x 不劣。
最小团覆盖:对最大独立集中的每个点
(由最大独立集的构造方案可以证明所有点都被覆盖到,再利用最大独立集大小不超过最小团覆盖大小可以证明最优性。)
进阶内容可以看 yhx 的博客。
区间图
在区间图
可以证明区间图一定是弦图,因此可以套用弦图算法解决区间图问题。
另外值得一提的是区间图不需要使用 MCS 求完美消除序列,将所有区间按右端点从小到大排序后的结果就是完美消除序列。
E. 平面图
概念与基本性质
对于一张图,若可以在平面上画出
-
可以由欧拉公式推理得到任意平面图中
m\le 3n-6 。 -
由库拉托夫斯基定理,
G 是平面图当且仅当其不存在同胚于K_5 或K_{3,3} 的子图。
事实上,平面图判定可以做到线性,不过让我先咕一会儿。
对偶图与最小左转法
对于任意平面图
-
- 当且仅当
G 中两个面之间有公共边时,G' 中的两个对应点间有边。
最小左转法用于平面图转对偶图。
大致过程是把每条无向边拆成两条有向边,然后确定每条有向边所在的面,并把一条边所在的面和其反向边所在的面相连。
确定面时把每个点出发的所有边逆时针排序,扫一遍所有边,扫到一条边的时候把这条边所属面的所有边都遍历一遍,并确定这些边都属于此面。其中“遍历一遍”就是在走到边
具体实现时正反向边可以使用 i ^ 1 的技巧。
将平面图对偶后,举 P3249 为例,我们可以刻画若干边所包围的多边形的所有面的性质(需要有可减性)。
我们以最外面面积无穷的区域为根跑出对偶图的一棵生成树,然后遍历这个多边形的边在对偶图中所对应的边。
若这条边是非树边就无需考虑,否则按照方向考虑 "加上其子树的贡献" 或者 "减去其子树的贡献"。
可以感性理解一下这样就可以得到答案,证明大概可以考虑若一棵子树根在区域内而有点在区域外,那么路径上一定有边在多边形的边界上,严谨证明不会。
实现有点阿拉丁的,Code。
F. 偏序与 Dilworth 定理
哈斯图
对于严格偏序关系
可以发现,哈斯图就是把偏序关系图最简化了,其余信息可以通过传递闭包得到。
Dilworth 定理
对于任意有限偏序集,其最长反链大小等于最小链覆盖大小。
(反链指任意两个元素都不可比的集合。)
证明:
首先最小链覆盖大小至少为最长反链大小,否则一定存在一条链中有两个属于反链的点,矛盾。
接下来我们归纳构造之。考虑删去集合
设最小链覆盖为
此外我们证明
于是我们考虑若
G. 斯坦纳树
问题是求一张图上包含一个给定的点集
可以考虑 DP,设
有两种转移,一是同一个位置的子集
H. K 元环计数
这里指无向图
此处的做法均基于边数,也存在一些复杂度形如
三元环计数
把所有边定向,把所有边从度数大的点连向度数小的(相同则人为定一个点的顺序),容易发现在的图是 DAG。
不难发现,此时所有点的入度是
由此,我们枚举
由于每个点作为
四元环计数
我们将图用同样的方法转成 DAG,然后计数(而不是像上面一样枚举出所有环)。
具体地我们枚举
这个做法的唯一问题就是会把
后期. 图论进阶算法
A. 最小树形图
问题的形式是:对于一张有向图
朱刘算法
首先需要一个引理:我们对于图中的每个点
我们证明,存在最小树形图方案使得对于每个
这个证明可以使用调整法实现,大致思路是对每个环找到树中 DFS 序最小的一个点
若边