弦图/最大势算法(MCS)
弦图
啥叫弦图?
定义弦为一个环中连接不相邻两个点的边。
弦图为一个图,其中任意一个长度大于\(3\)的环都包含至少一条弦。
换句话说:弦图最大的没有弦的环大小小于\(3\)
下面是一些定义:
团:团是一个点集,满足集合内的点两两连边。
最大团:图中点数最多的团。
极大团:不是其他团的子集的团。
诱导子图/导出子图:一个子图,它的点集是原图的点集的子集,并且满足原图上的边假如两端都在这个子图内,那么这条边也在子图内。
单纯点:设\(N(x)\)表示与\(x\)相连的点,若\(x+N(x)\)的诱导子图为一个团,则称\(x\)为一个单纯点。
完美消除序列:按照序列顺序逐个删去原图中的每个点,删去的这个点在残图中为单纯点。
极小点割集:是对于一个不相邻点对的一个点集,删除这个点集之后,这个点对不相邻。
重要定理
参考了OIWIKI上的内容。
具体证明可以参考OIWIKI。
这里只是对OIWIKI上的内容的直观化表述。
- 弦图的任意导出子图仍为弦图
证明:
考虑一张图的导出子图不是弦图。
那么这张图包含一个大小为\(4\)的环,我们还原图的时候是加上一个点,并且把这个点与一些点连边,但是并不会在这个环上加边,于是新图不可能是弦图。
- 任意一个弦图至少有一个单纯点
- 一张图是弦图当且仅当这张图有完美消除序列
证明:
1.如果一张图不是弦图,那么一定存在一个大于3元的环,想要删除这个环上的某个点时,它跟相邻的两个点就不能构成团。
2.假如一个图是弦图,那么删除一个单纯点之后,剩下的是一个导出子图,仍然是一个弦图,直到删完。
- 极小点割集的相邻点在删去极小点割集后必定同时具有与\(u,v\)联通的点
证明:
假如一个点,它的相邻点只与一边联通,那么可以不删去这个点,于是极小点割集可以变得更小,与极小矛盾。
- 任意两点的极小点割集必定为一个团
证明:
考虑证明点割集内两两均有连边。
设两个点为\(u, v\),点割集内有\(x,y\)两点,\(u\)所在的连通块为\(A\), \(v\)所在的连通块为\(B\)。
设\(x\)与\(A\)内的\(x_1\)相连,\(x\)与\(B\)内的\(x_2\)相连。
设\(y\)与\(A\)内的\(y_1\)相连,\(y\)与\(B\)内的\(y_2\)相连。
那么构造一个环,\(x_1->x ->x_2...y_2->y->y_1...x_1\)。
在最好的情况下,\(x_1==y_1\), \(x_2 == y_2\)。
这时候就构造出了一个四元环:只有两种弦:\(x_1 -> x_2\),\(x->y\)。
假如\(x_1 -> x_2\),那么这个集合不是点割集,因为\(A\)和\(B\)直接相连。
否则证明\(x,y\)之间有边。
最大势算法
具体证明见OIWIKI,这里只讲做法。
最大势(MCS)算法,用于求出一张弦图的完美消除序列。
倒序对每个点标号,对每个点维护有多少个已经被标号的点与之相连。
每次取出相连点数最大的点标号,标号序列的逆序即为所求完美消除序列。
由于每次取出点,相邻点最多增加一个,所以个数最大不超过\(n\),那么开\(n\)个桶即可。
int maxn = 0;
for(int i = 1; i <= n; i++) v[0].push_back(i);
for(int cnt = n; cnt; cnt--) {
int x, f = 0;
while(!f) {
for(int i = v[maxn].size() - 1; ~i; i--) {
x = v[maxn][i];
if(rnk[x]) v[maxn].pop_back();
else {f = 1; break;}
}
if(!f) maxn--;
}
id[cnt] = x; rnk[x] = cnt;
for(int i = head[x]; i; i = nxt[i]) {
if(!rnk[ver[i]]) {
v[++num[ver[i]]].push_back(ver[i]);
maxn = max(maxn, num[ver[i]]);
}
}
}
弦图判定
Luogu: SP5446 FISHNET - Fishing Net
既然MCS算法是求出完美消除序列,那么要是这张图不是弦图怎么办?
没关系,有了完美消除序列,我们就可以判断这张图是否为弦图。
我们遍历这个序列,对于每个点,我们只需要知道与这个点相连并且排在后面的点是否构成一个团。
不过朴素的遍历方法仍旧是\(O(n^3)\),但是我们发现我们只需要判断与这个点相连并且最靠前的这个点是否与其他点相连即可。
原因?
记这个点为\(x\),\(x\)后面的点为\(y\),那么在将来的序列中,必定会访问到\(x\),那么\(x\)的后面一个点为\(y\),就会判断\(y\)是否与\(x\)相连的其他点相连,一直判断下去就可以判断完整个团,这样子时间复杂度是\(O(n + m)\)。
int ff = 1;
for(int ID = 1; ID <= n; ID++) {
int x = id[ID];
siz = 0;
for(int i = head[x]; i; i = nxt[i]) {
if(rnk[ver[i]] > rnk[x]) tmp[++siz] = ver[i], con[ver[i]] = 0;
if(siz > 1 && rnk[tmp[siz]] < rnk[tmp[1]]) swap(tmp[siz], tmp[1]);
}
for(int i = head[tmp[1]]; i; i = nxt[i])
con[ver[i]] = 1;
for(int i = 2; i <= siz; i++) {
if(!con[tmp[i]]) {
ff = 0;
break;
}
}
if(!ff) break;
}
if(ff) printf("Perfect\n\n");
else printf("Imperfect\n\n");
弦图的极大团
极大团的定义是:这个团不为其他团的子集。
记\(N(x)\)为在\(x\)后面并且与\(x\)相连的点。
注意到在消除序列上\(x+N(x)\)为一个团。
假如说前面还有一个点为\(y\)为\(x\)团的父亲集合,那么一定满足\(N(y)\)包含\(x\)。
假如\(N(y)\)的第一个点为\(x\),那么\(x\)必定不是极大团。
只要预处理一个\(fst[x]\),然后扫描一遍即可,OIWIKI上有代码。
弦图的团数/色数
Luogu: P3196 [HNOI2008]神奇的国度
弦图上有定理:团数=色数
显然团上每个点的颜色都不能是一样的,但是具体证明不太会。。OIWIKI上写的也不清楚,于是就不知道了,反正可以用。
团数为最大团的点数。
如果求染色方案,则按照完美序列从前往后暴力染色即可。
否则的话就是最大团大小。
int maxsiz = 1;
for(int q = 1; q <= n; q++) {
int x = id[q], ntot = 0;
for(int i = head[x]; i; i = nxt[i]) {
if(rnk[ver[i]] > rnk[x])
ntot++;
}
maxsiz = max(maxsiz, ntot + 1);
}
弦图的最小团覆盖/独立集大小
Luogu: P3852 [TJOI2007]小朋友
显然一个团内只能取一个点,而独立集大小即为最小团覆盖。
对于序列中的每个点,把之后的点全部打上标记即可。
int ans = 0;
for(int q = 1; q <= n; q++) {
int x = id[q];
if(!vis[x]) {
ans++;
for(int i = head[x]; i; i = nxt[i])
vis[ver[i]] = 1;
}
}