【瞎口胡】弦图
弦图,我真的好喜欢你啊,为了你,我要求出你的完美消除序列!
本篇目参考 OI-Wiki,修改了部分证明。
定义
弦图是满足以下条件的无向图:任意长度大于 \(3\) 的环上都有一条「弦」,即连接环上不相邻两点的边。
让我们明确以下新定义:
- 团:完全子图。
- 极大团:不是其它团子图的团。
- 最大团:点数最大的团。
- 团数:记作 \(\omega (G)\),表示最大团点数。
- 最小染色:使用最小的颜色对点进行染色使得相邻两点颜色不同。
- 色数:记作 \(\chi(G)\),表示最小染色使用的颜色数量。
- 最大独立集:记作 \(\alpha(G)\),最大的内部没有连边的点集。
- 最小团覆盖:用最少的团覆盖所有点,使用的团的数量记为 \(\kappa (G)\)。
- 邻域:记作 \(N(x)\),表示与一个点相邻的点构成的集合。
基本性质
-
引理 1:团数 \(\omega (G) \leq \chi (G)\) 色数
考虑对最大团的导出子图进行染色,色数是 \(\omega(G)\)。
-
引理 2:最大独立集 \(\alpha(G) \leq \kappa(G)\) 最小团覆盖数
每个团最多选一个点。
-
引理 3:弦图的导出子图一定是弦图。
显然。
-
引理 4:弦图的导出子图一定不是点数大于 \(3\) 的环。
同引理 3。
点割集
\(u,v\) 之间的点割集指的是一个点集,使得删掉它们后 \(u,v\) 不连通。最小点割集是满足条件的点集中,点数最小的。
-
引理 5:考虑删掉最小点割集 \(S\) 之后,原图分成了至少两个联通块。设包含 \(u,v\) 的联通块分别是 \(V_1,V_2\),那么对于任意 \(a \in S\),一定存在 \(x,y \in N(a)\) 使得 \(x \in V_1,y \in V_2\)。
-
引理 6:最小点割集 \(S\) 一定是一个团。
\(|S|\leq 1\) 时是平凡的。对于 \(|S|>1\),此时最小点割集上一定存在两点 \(x,y\),设 \(N(x),N(y)\) 在 \(V_1,V_2\) 中的点分别为 \(x_1,x_2\) 和 \(y_1,y_2\)。
点对 \((x_1,y_1)\) 和 \((x_2,y_2)\) 之间有最短路。考虑 \(x\) 和 \(y\) 在路径上只经过 \(V_1,V_2\) 时的最短路,不难发现分别为 \(x \to x_1 \to \cdots \to y_1 \to y\) 和 \(x \to x_2 \to \cdots \to y_2 \to y\),此时存在长度大于等于 \(4\) 的环 \(x \to x_1 \cdots \to y_1 \to y \to y_2 \to \cdots \to x_2 \to x\),根据弦图的定义,该环上一定存在一条弦。
注意到,弦不能连接 \(V_1,V_2\) 中各一个点(否则不是点割集);也不能连接 \(V_1,V_2\) 内部的两个点(否则不是最短路); 更不能连接 \(V_1,V_2\) 内部的一个点和 \(x,y\) 中的某一个(同理)。因此,这条弦只能连接 \(x,y\)。
于是对于任意 \(x,y\) 应用上述论述,我们得到了任意 \(x,y\) 之间都有边,于是它是一个团。
单纯点
单纯点指的是满足以下条件的点 \(x\):它和它的邻域,即 \(\{x\}+N(x)\)构成一个团。
-
引理 7:任何一个弦图都有至少一个单纯点,不是完全图的弦图有至少两个不相邻的单纯点。
考虑对于每个联通块单独证明。
当图的大小 \(\leq 3\) 时,引理成立。
如果图的大小至少为 \(4\),完全图的部分也平凡的。接下来,如果图不是完全图,则一定存在一条边 \((u,v) \notin E\)。设 \(I\) 是 \(u,v\) 间的最小点割集,\(A,B\) 为删去 \(I\) 后 \(u,v\) 各自所在的联通块。
不失一般性,考虑 \(A\) 一侧,设 \(L=A+I\),如果 \(L\) 是完全图,则 \(u\) 是单纯点;如果不是,那么 \(L\) 上有两个不相邻的单纯点。因为 \(I\) 是完全图,其上任意两点相邻,因此 \(A\) 上至少有一个单纯点。
考虑将 \(A\) 上的单纯点拓展到全图,如果邻域没有变化,那么它就是原图的一个单纯点。不难发现,\(A\) 上每个点的邻域一定是 \(L\) 中的一部分点,因此拓展到全图后邻域没有变化。
对于 \(B\) 侧同样考虑,我们得到了两个不相邻的单纯点,引理 7 得证。
完美消除序列
对于 \(n\) 个点的弦图,完美消除序列指的是这样一个序列 \(v_1,v_1,\cdots,v_n\),它满足对于每个 \(i\),\(v_i\) 在 \(\{v_i,v_i+1,\cdots,v_n\}\) 的导出子图中是单纯点。
-
引理 8:一个无向图是弦图当且仅当其存在完美消除序列。
考虑如果一个无向图是弦图,每次在点数为 \(n-1\) 的弦图的完美消除序列的最前方加入一个单纯点就可以得到点数为 \(n\) 的弦图的完美消除序列、
如果一个非弦图的无向图存在大小大于 \(3\) 的环但有完美消除序列,设在完美消除序列中出现的第一个在环上的点是 \(v\),它在环上和 \(v_1,v_2\) 相邻,根据单纯点的性质可知 \(v,v_1,v_2\) 构成一个团,于是存在弦 \((v_1,v_2)\),矛盾。
-
MCS 最大势算法
考虑从 \(n\) 到 \(1\) 逆序给节点标号,设 \(l_x\) 表示和 \(x\) 相邻的已标号点数的数量,每次选出 \(l\) 最大且未被标号的节点进行标号。正确性显然。
一个图是弦图当且仅当求出的序列是完美消除序列。读者自证不难,我不会。
时间复杂度 \(O(n+m)\)(使用链表)或者多一个 \(\log\)。
应用
重新定义 \(N(x)\) 表示 \(x\) 的邻域中完美消除序列位置在自己之后的点的数量。
-
找最大团
最大的 \(\{x\}+N(x)\)。
-
色数
按完美消除序列从后往前依次给每个点染色,给每个点染上可以染的最小颜色。
考虑此时方案数 \(t \geq \chi(G)\),且瓶颈在于最大团上的染色,因此 \(t=\omega(G)\),由引理 1得\(t \leq \chi(G)\),因此 \(t = \chi (G)\)。
\(\chi(G)\) 就是最大团大小。
-
最大独立集 / 最小团覆盖
最大独立集:完美消除序列从前往后,选择所有没有与已经选择的点有直接连边的点。
最小团覆盖:独立集中的每个点和邻域构成的团,即 \(\{\{u_1\}+N(u_1),\cdots\{u_{t}\}+N(u_{t})\}\)。
设上面的方法求出的答案为 \(t\),那么 \(\kappa(G) \leq t \leq \alpha(G)\),引理 2 得 \(\alpha(G) \leq \kappa (G)\),于是 \(\alpha(G) = \kappa(G) = t\)。
例题 1 [HNOI2008]神奇的国度
题意
求弦图色数。
\(1 \leq n \leq 10^4. 1 \leq m \leq 10^6\)
题解
按照结论模拟即可。
# include <bits/stdc++.h>
const int N=10010,INF=0x3f3f3f3f;
struct Node{
int id,w;
bool operator < (const Node &rhs) const{
return w<rhs.w;
}
};
std::priority_queue <Node> Q;
std::vector <int> G[N];
int n,m;
int rk[N],p[N],lb[N];
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
int main(void){
n=read(),m=read();
for(int i=1;i<=m;++i){
int u=read(),v=read();
G[u].push_back(v),G[v].push_back(u);
}
for(int i=1;i<=n;++i) Q.push((Node){i,0});
int cur=n;
while(!Q.empty()){
int i=Q.top().id;
Q.pop();
if(rk[i]) continue;
rk[i]=cur,p[cur]=i,--cur;
for(auto v:G[i]){
if(!rk[v]) ++lb[v],Q.push((Node){v,lb[v]});
}
} // MCS 算法
int mx=0;
for(int i=1;i<=n;++i){
int cur=1;
for(auto v:G[i]) if(rk[i]<rk[v]) ++cur;
mx=std::max(mx,cur);
}
printf("%d",mx);
return 0;
}
例题 2 [TJOI2007]小朋友
题意
求弦图最大独立集。
\(1 \leq n \leq 200,0 \leq m \leq \dfrac{n(n-1)}{2}\)
题解
按照题意模拟即可。
# include <bits/stdc++.h>
const int N=210,INF=0x3f3f3f3f;
struct Node{
int id,w;
bool operator < (const Node &rhs) const{
return w<rhs.w;
}
};
std::priority_queue <Node> Q;
std::vector <int> G[N];
int n,m;
int rk[N],p[N],lb[N];
bool vis[N];
inline int read(void){
int res,f=1;
char c;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
res=c-48;
while((c=getchar())>='0'&&c<='9')
res=res*10+c-48;
return res*f;
}
int main(void){
n=read(),m=read();
for(int i=1;i<=m;++i){
int u=read(),v=read();
G[u].push_back(v),G[v].push_back(u);
}
for(int i=1;i<=n;++i) Q.push((Node){i,0});
int cur=n;
while(!Q.empty()){
int i=Q.top().id;
Q.pop();
if(rk[i]) continue;
rk[i]=cur,p[cur]=i,--cur;
for(auto v:G[i]){
if(!rk[v]) ++lb[v],Q.push((Node){v,lb[v]});
}
}
int mx=0;
for(int i=1;i<=n;++i){
if(vis[p[i]]) continue;
++mx,vis[p[i]]=true;
for(auto v:G[p[i]]) if(rk[p[i]]<rk[v]) vis[v]=true;
}
printf("%d",mx);
return 0;
}