【算法竞赛学习笔记】弦图和区间图

19 篇文章 0 订阅
11 篇文章 0 订阅

title : 弦图
date : 2022-4-22
tags : ACM,图论
author : Linno


弦图

任意长度大于3的环都有一个弦的图称为弦图。很多在一般图上的NP-Hard问题在弦图中都有优秀的线性时间复杂度解法。

基本概念

:连接环中不相邻两点的边。

子图:点集和边集均为原图点集和边集的子集的图。

:完全子图,即满足任意两点都恰有一条边相连的子图。

导出子图:点集为原图点集子集,边集为所有满足两端点均在选定点集中的图。

最大独立集:最大的点集使得点集中任意两点都没有边直接相连。

最小团覆盖数:用最少的团覆盖所有的点。

单纯点:设 N ( v ) N(v) N(v)表示与点 v v v相邻的点集。一个点称为单纯点当 { v } + N ( v ) \{v\}+N(v) {v}+N(v)的诱导子图为一个团。任何一个弦图都有至少一个单纯点,不是完全图的弦图至少有两个不相邻的单纯点。

完美图:一个图G称为完美图若它的每一个诱导子图都满足 ω ( G ) = χ ( G ) \omega(G)=\chi(G) ω(G)=χ(G)

伴完美图:一个图G称为伴完美图若它的每一个诱导子图都满足 α ( G ) = κ ( G ) \alpha(G)=\kappa(G) α(G)=κ(G)

定理

①:团数小于等于色数。 w ( G ) ≤ χ ( G ) w(G)\le \chi(G) w(G)χ(G)

对最大团的导出子图进行染色,至少需要 w ( G ) w(G) w(G)种。

②:最大独立集数小于等于最小团覆盖数。$\alpha(G)\le \kappa(G) $

每个团至多选择一个点。

③:弦图的任意导出子图一定都是弦图。

④: 弦图的任意导出子图一定不可能是一个点数大于3的环。(与③同理)

⑤最大独立集数 = 最小团覆盖数

⑥完美图属于伴完美图

⑦弦图属于完美图

弦图判断

朴素算法

依次判断 { v i + 1 , . . . , v n } \{v_{i+1},...,v_n\} {vi+1,...,vn}中所有与 v i v_i vi相邻的点是否构成了一个团。

时间复杂度 O ( ∑ ( d e g ( v ) ) 2 ) = O ( n m ) O(\sum (deg(v))^2)=O(nm) O((deg(v))2)=O(nm)

优化后的算法

{ v i + 1 , . . . , v n } \{v_{i+1},...,v_n \} {vi+1,...,vn}中所有与 v i v_i vi相邻的点依次为 v j 1 , . . . , v j k v_{j1},...,v_{jk} vj1,...,vjk

只需判断 v j 1 是 否 与 v j 2 , . . . , v j k 相 邻 即 可 。 v_{j1}是否与v_{j2},...,v_{jk}相邻即可。 vj1vj2,...,vjk

时间复杂度 O ( n + m ) O(n+m) O(n+m)

//bzoj1242 Zju1015 Fishing Net弦图判定
#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=1010,M=4e6+10;

int n,m,g[N][N],q[N],f[N],rk[N];
int head[N],to[M],nxt[M],tot;
vector<int>nt[N];bool vs[N];

inline void lk(int u,int v)
{to[++tot]=v;nxt[tot]=head[u];head[u]=tot;}

void MSC()
{
	int i,j,x,y,bst=0;
	for(i=1;i<=n;++i) lk(0,i);
	for(j=n;j>0;--j){
		for(;;){
			for(int &i=head[bst];i;i=nxt[i])
				if(!vs[to[i]]) break;
			if(head[bst]){
				x=to[head[bst]];q[j]=x;rk[x]=j;vs[x]=true;
				for(i=nt[x].size()-1;i>=0;--i) if(!vs[nt[x][i]]){
					y=nt[x][i];f[y]++;lk(f[y],y);
					bst=max(bst,f[y]);
				}
				break;
			}else bst--;
		}
	}
}

int stk[N],top;

bool ck()
{
	int i,j,x,y,mng,mnv;
	for(j=n;j>0;--j){
		x=q[j];top=0;mng=n+1;
		for(i=nt[x].size()-1;i>=0;--i) if(rk[nt[x][i]]>j){
			y=nt[x][i];stk[++top]=y;
			if(rk[y]<mng) mng=rk[y],mnv=y; 
		}
		if(mng==n+1) continue;
		for(i=1;i<=top;++i) if(stk[i]!=mnv)
		 if(!g[mnv][stk[i]]) return false;
	}
	return true;
}

int main(){
	int i,j,x,y;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;++i){
		scanf("%d%d",&x,&y);
		g[x][y]=g[y][x]=1;
		nt[x].pb(y);nt[y].pb(x);
	}
	MSC();
	if(ck()) printf("Perfect");
	else printf("Imperfect");
	return 0;
}

结论:弦图判定的问题可以在线性时间内解决!

完美消除序列

一个完美消除序列是一个排列 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an,满足对于任意的 i i i,都有 a i , a i + 1 , . . . , a n a_i,a_{i+1},...,a_n ai,ai+1,...,an形成的导出子图与 a i a_i ai相邻的点两两之间有边。一个图是弦图的充要条件是它有一个完美序列。

朴素算法求完美消除序列

①每次找一个单纯点,加入到完美消除序列中。

②将v以及相关的边从图中删掉。

③重复以上过程直到所有点都被删除或者不存在单纯点v(图不是弦图)。

MCS算法求完美消除序列

最大势算法是一种可以再 O ( n + m ) O(n+m) O(n+m)的时间内求出无向图的完美消除序列的方法。具体步骤为:

①逆序给结点编号,即按从n到1的顺序给点标号。

②设 l a b e l x label_x labelx表示第 x x x个点与多少个已经标号的点相邻,每次选择 l a b e l label label值最大的未标号结点进行标号。

③用链表维护对于每个 i i i,满足 l a b e l x = i label_x=i labelx=i的x。

④由于每条边对 ∑ i = 1 n l a b e l i \sum_{i=1}^n label_i i=1nlabeli的贡献最多是2,时间复杂度为 O ( n + m ) O(n+m) O(n+m)

除此之外,还有LexBFS算法(字典序广度优先搜索)。

弦图的色数

给定一个n个点m条边的弦图,求它的色数。

思路:先求出完美消除序列,然后贪心倒着染色。与当前点相邻的染过色的点显然颜色互不相同,因此可以证明是对的。枚举颜色数 O ( m ) O(\sqrt m) O(m ),所以总复杂度为 O ( n m ) O(n\sqrt m) O(nm )

模板 P3196 [HNOI2008]神奇的国度

按完美消除序列从后往前依次给每个点染色,给每个点染上可以染的最小颜色。

//#pragma GCC optimize("Ofast", "inline", "-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define int long long
using namespace std;
const int N=2e5+7;
const int mod=1e9+7;

//int read(){	int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-') f=f*-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}return x*f;}

int n,m,a[N],c[N],l[N],r[N],col[N],vis[N];
bool b[N];
vector<int>G[N]; 

void get_array(){
	for(int i=0;i<=2*n;i++){
		l[i]=r[i]=-1;
	}
	for(int i=1,t=0;i<=n;i++){
		l[n+i]=t,r[t]=n+i,t=n+i;
	}
	int mx=0;
	for(int k=1;k<=n;k++){
		while(r[mx]==-1) mx--;
		int u=r[mx]-n,t=r[u+n];
		l[t]=mx,r[mx]=t;
		a[k]=u,b[u]=1;
		for(int i=0,v,lx,rx;i<G[u].size();i++){
			v=G[u][i];
			if(b[v]) continue;
			lx=l[n+v],rx=r[n+v];
			r[lx]=rx,l[rx]=lx,c[v]++;
			l[n+v]=r[n+v]=-1;
			if(~r[c[v]]) l[r[c[v]]]=n+v,r[n+v]=r[c[v]];
			r[c[v]]=n+v,l[n+v]=c[v];
		}
		mx++;
	}
}

void Solve(){
	cin>>n>>m;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	get_array();
	int ans=0;
	for(int k=1,i;k<=n;k++){
		i=a[k];
		for(int j=0;j<G[i].size();j++) vis[col[G[i][j]]]=i;
		for(int j=1;j<=n;j++){
			ans=max(ans,j);
			if(vis[j]!=i){
				col[i]=j;
				break;
			}
		}
	}
	cout<<ans<<"\n";
}

signed main(){
	int T=1;
	//cin>>T;
	while(T--){
		Solve();
	}
	return 0;
}

区间图

给定一些区间,定义一个相交图为每个顶点表示一个区间,两个点有边当且仅当两个区间的交集非空。

一个图为区间图当它是若干个区间的相交图。

性质

①区间图一定是弦图

②给定n个区间的区间图为G,G的一个完美消除序列为:将所有的区间按照右端点从小到大排序。

应用

注:下面两个例子写起来都是很简单的贪心,不过相信大多数人开始时都没有相同是怎么证明的。

区间图的最大独立集

给定n个区间,要求选择最多的区间使得区间不互相重叠。

思路:将所有的区间按照右端点从小到大排序,依次处理每个区间,如果与已选区间没有重叠则选择该区间。复杂度 O ( n l o g n ) O(nlogn) O(nlogn)

区间图的最小染色

Tetris:有n个积木,高度均为1,第i个积木的宽度范围是 [ L i , R i ] [L_i,R_i] [Li,Ri],选择一个积木的下落顺序使得最后积木总高度尽可能小。

思路:将所有的区间按照右端点从大到小排序,依次处理每个区间,选择一个可以染的最小的颜色染色。线段树或堆维护,时间复杂度 O ( n l o g n ) O(nlog n) O(nlogn)

参考资料

OI-Wiki

弦图和区间图-陈丹琦

https://blog.csdn.net/corsica6/article/details/88979383

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RWLinno

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值