弦图/最大势算法(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

xA内的x1相连,xB内的x2相连。
yA内的y1相连,yB内的y2相连。

那么构造一个环,x1>x>x2...y2>y>y1...x1
在最好的情况下,x1==y1, x2==y2

这时候就构造出了一个四元环:只有两种弦:x1>x2x>y

假如x1>x2,那么这个集合不是点割集,因为AB直接相连。
否则证明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(n3),但是我们发现我们只需要判断与这个点相连并且最靠前的这个点是否与其他点相连即可。

原因?

记这个点为xx后面的点为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)为一个团。
假如说前面还有一个点为yx团的父亲集合,那么一定满足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;
    }
}
posted @   _onglu  阅读(952)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架
点击右上角即可分享
微信分享提示