特殊图(竞赛图、弦图)(待重构)
竞赛图
竞赛图定义与性质
定义 1.1
竞赛图是简单有向图,满足任意两点之间都有且仅有一条边。
性质 1.2
竞赛图缩点之后的任唯一拓扑序是全序集。
证明:
设竞赛图 。
显然,若点 不在同一个强连通分量,在新图上随意指定 dfs 序 。根据竞赛图,若 ,那么一定有 ,因此 是全序的。
值得一提的是,这证明缩点之后是“链加上一些前向边”。以下,我们认为“左边”是拓扑序更小的那一端,“中间的”是非最左最右的点。
定理 1.3 Camion-Moon 定理
强连通竞赛图有哈密尔顿环。
证明:
我们归纳构造这个哈密尔顿环。
显然成立。
否则,删去任意点 ,会分出若干强连通分量,根据性质 1.1,这些强连通分量“成链”。
根据归纳假设,这些强连通分量内部有哈密尔顿环。“合并”这些环得到删去 后的图的“从左到右”的哈密尔顿路。
根据原图是强连通图, 连向最左的强连通分量,最右的连向 。这样合并进 就得到了哈密尔顿环。
如果只有缩出来一个强连通分量,环一定能够有一个地方插的进去 。
算法 1.4
求强连通竞赛图哈密尔顿环。
上述构造方法的实现是 的(至少我不知道更少的做法)。
首先容易找到哈密尔顿路:增量插入即可。
首先找到第一个连向起点的点,令起点为 ,此点为 ,对于接下来的每个点 ,找到它连向前面的第一个点 ,现在环是 。所以令 。
这是它的实现。(P3561)
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e3+5;
int n,low[maxn],dfn[maxn],bel[maxn],st[maxn],vis[maxn],tp=0,T=0,cnt=0;
vector<int> pt[maxn],ans[maxn];
int d[maxn][maxn],nxt[maxn];
void tarjan(int u){
dfn[u]=low[u]=++T,st[++tp]=u,vis[u]=1;
for(int v=1;v<=n;v++){
if(!d[u][v])continue;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}else if(vis[v])low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
++cnt;
while(1){
int x=st[tp--];
pt[cnt].push_back(x);
bel[x]=cnt,vis[x]=0;
if(x==u)break;
}
}
}
void solve(int a){
if(pt[a].size()==1)return ;
int s,t,x;
s=t=pt[a][0];
for(int i=1;i<pt[a].size();i++){
x=pt[a][i];
if(d[t][x]){
nxt[t]=x;t=x;
continue;
}
if(d[x][s]){
nxt[x]=s;s=x;
continue;
}
for(int j=s;j!=t;j=nxt[j]){
if(d[j][x]&&d[x][nxt[j]]){
nxt[x]=nxt[j],nxt[j]=x;
break;
}
}
}//Hamilton path
t=0;
for(int i=nxt[s];i;i=nxt[i]){
if(d[i][s])t=i;
else if(t){
for(int j=s;j!=t;j=nxt[j]){
if(d[i][nxt[j]]){
x=nxt[j];nxt[j]=nxt[t],nxt[t]=s;
s=x,t=i;
break;
}
}
}
}//Hamilton circle
nxt[t]=s;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=2;i<=n;i++){
for(int j=1;j<i;j++){
int x;cin>>x;
if(x)d[j][i]=1;
else d[i][j]=1;
}
}
for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
for(int i=1;i<=cnt;i++)solve(i);
int x,k;
for(int i=1;i<=n;i++){
x=i,k=bel[i];
while(1){
ans[i].push_back(x);
if(pt[k].size()==1){
if(k==1)break;
--k,x=pt[k][0];
continue;
}
for(int j=nxt[x];j!=x;j=nxt[j])ans[i].push_back(j);
if(k==1)break;
k--,x=pt[k][0];
}
cout<<ans[i].size()<<" ";
for(auto u:ans[i])cout<<u<<" ";
cout<<endl;
}
return 0;
}
定理 1.5 Redei 定理
竞赛图有哈密尔顿路。
同上证明。
性质 1.6
一个有向图 是泛圈的,当且仅当 , 有 元环。
强连通竞赛图是泛圈的。
证明:
显然 成立。
只需证明:, 阶竞赛图有 元环。
类似于上面的构造,如果链上的这些强连通分量大小有一个大于一就绕过他的一个点,显然是可行的。
否则,强连通分量大小均为一,显然我可以跨过链上来越过一个点。
定理 1.7 Landau 定理
给定一不降序列 ,满足
存在一竞赛图使得节点出度序列为 的充要条件是
证明:
必要性显然,下证明充分性,即可以构造出这样的竞赛图。
首先设初始竞赛图为 ,这样的出度序列 满足
调整过程中,这个条件将一直被满足。
找到最小的 满足 (找不到就完成了),最小的 满足 (显然其存在且 )。
一定有 ,或 。此时,反转任意一个这样的边一次。
以上操作显然不破坏性质。一直做去,有限次就能得到正确结果。
性质 1.8
给定竞赛图不降出度序列 ,其强连通分量个数为
证明:
首先证明其大于等于强连通分量个数。
缩点后图成链,显然,靠左的强连通分量中每个点的出度都比靠右的强连通分量里任意点的出度大。
那么显然,对于每个非最右强连通分量,
且 和每个靠右的强连通分量里面的点对应。
显然 是不同的,以及 也满足这个,就证明了其大于等于强连通分量个数。
然后证明其小于等于强连通分量个数。
对于所有此式成立的 , 不向外连边,所以有 代表的节点每个都向 代表的节点的每个连边。
所以这样的 被强连通分量的边界对应。还有 。这样就证明了其小于等于强连通分量个数。
证毕。
算法 1.9
无权竞赛图最小割。
设目前左右集合为 ,初始 。
考虑把 从 换到 的变化量 。
因此只需加入一段按 排序后前缀即可。
具体地,记
其中 升序排序,且去除 。
答案即为 。
具体来说,我们可以依据 对 分段,然后求解 RMQ 问题。
可以 在线回答。
例题
P4233
总哈密顿回路是容易计算的:
只需统计强连通竞赛图数量。而由于竞赛图缩点后的强连通分量是链,并且确定链即可确定竞赛图,因此有关系 ,其中 E 是竞赛图 EGF,F 是强连通竞赛图 EGF。多项式求逆即可。
CF1268D
引理 2.1
阶强连通竞赛图有 阶强连通导出子图。
证明:
随意取一个点 ,设删去之后缩点从左到右为 。
如果 ,即证。
否则,如果所有 ,,把中间的一个 remove 即可。
否则存在 ,将这个强连通分量的(被用作原图哈密尔顿回路的)哈密尔顿路径 的 remove,把它前面一个点作为路径结尾。显然这样不破坏图有哈密尔顿路,即证。
定理 2.1
对于点数 的图,一次操作可以解决问题。
证明:
如果存在一个强连通分量大小大于 :适用引理 2.1,把那个点反转,容易发现图现在强连通。
如果有超过 个强连通分量:反转中间的强连通分量随便一个点即可。
否则,点数 。
实现上,我们不需要通过刚刚的过程求出到底是哪个点,枚举然后利用性质 1.3 判断即可。
// Walawala 我是治程猴子
// Problem: Invertation in Tournament
// Platform: Luogu
// URL: https://www.luogu.com.cn/problem/CF1268D
// Memory Limit: 250 MB
// Time Limit: 3000 ms
// Author:British Union
// Long live UOB and koala
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
const int maxn=2005,INF=0x3f3f3f3f;
bool d[maxn][maxn];
char c[maxn];
int p[maxn],n,b[maxn];
bool check(){
for(int i=1;i<=n;i++)b[i]=p[i];
sort(b+1,b+n+1);int S=0,C=0;
for(int i=1;i<=n;i++){
S+=b[i];
if(S==i*(i-1)/2)C++;
}
return C==1;
}
int fac(int x){
if(x==0)return 1;
return x*fac(x-1);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>c+1;
for(int j=1;j<=n;j++)d[i][j]=c[j]-'0';
for(int j=1;j<=n;j++)p[i]+=d[i][j];
}
if(check()){
cout<<"0 1"<<endl;
return 0;
}
if(n>6){
int cnt=0;
for(int i=1;i<=n;i++){
p[i]=n-1-p[i];
for(int j=1;j<=n;j++){
if(i==j)continue;
if(d[i][j])p[j]++;
else p[j]--;
}
if(check())++cnt;
p[i]=n-1-p[i];
for(int j=1;j<=n;j++){
if(i==j)continue;
if(d[i][j])p[j]--;
else p[j]++;
}
}
cout<<1<<" "<<cnt<<endl;
}else{
int Min=INF,cnt=0;
for(int i=0;i<(1<<n);i++){
for(int j=1;j<=n;j++){
if(i&(1<<j-1)){
for(int k=1;k<=n;k++){
if(d[j][k])d[j][k]=0,d[k][j]=1;
else d[j][k]=1,d[k][j]=0;
}
}
}
for(int j=1;j<=n;j++){
p[j]=0;
for(int k=1;k<=n;k++)p[j]+=d[j][k];
}
if(check()){
int r=__builtin_popcount(i);
if(r<Min){
Min=r,cnt=1;
}else if(r==Min)++cnt;
}
for(int j=1;j<=n;j++){
if(i&(1<<j-1)){
for(int k=1;k<=n;k++){
if(d[j][k])d[j][k]=0,d[k][j]=1;
else d[j][k]=1,d[k][j]=0;
}
}
}
}
if(Min==INF)cout<<-1<<endl;
else cout<<Min<<" "<<cnt*fac(Min)<<endl;
}
return 0;
}
CF1338E
引理 2.2
。
证明:
假设存在一条 到 的最短路径 。
则有 (否则非最短)。那么 三元环连向 ,不符合条件。
引理 2.3
原图缩点后只有最右边的强连通分量可能点数 。
证明:显然强连通分量大小 。否则发现这个强连通分量的任意三元环都会连向右边的任意点。
根据此引理,只有最右强连通分量的点之间存在不平凡的贡献。
(注:下面一部分似乎存在简单得多的方法数 dis,然而下面对结构进行了详细的分析,可以做某道 AGC)
引理 2.4
取最右边强连通分量 的导出子图 中入度最大(在之后的证明需要这个)的点 ,令 ,。
则 均是拓扑序全序集(“链”)
证明:
只需证明 均无环,之后由竞赛图可得。
显然 无环。
取任意 使得 。分割 为 。显然 不存在环。
中所有点必定全部指向 的点,否则若有 , 三元环指向 ,不符合要求。
那么 显然不含环, 间也不可能含环,证毕。
按拓扑序从左到右记 元素为 。
引理 2.5
记 。
均是 序列的后缀,且 。
证明:
先证明 是后缀。
如果 ,那么 三元环指向 。
再证明 是后缀。
反证,设 。
则如果 ,设 ,则 。
由 ,。
那么 三元环连向 ,矛盾。
否则,,则 。
又 , 三元环连向 ,矛盾。
由此二者容易得出 ,且成包含关系,否则不满足后缀性质。
证毕。
定理 2.2
。
证明:
如果 ,那么 。显然后者大于 ,可以找到路径 。
否则,。
容易发现,。
反证:设 ,则 ,则 入度比 大 ,与最大性矛盾(终于用到了最大性)。
证毕。
计算答案是容易的:最右强连通分量中, 间贡献为 , 内部中如果两个 相等一对贡献为 ,否则为 ,显然。
// Problem: JYPnation
// Platform: Luogu
// URL: https://www.luogu.com.cn/problem/CF1338E
// Memory Limit: 1000 MB
// Time Limit: 2000 ms
// Author:British Union
// Long live UOB and koala
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxn=8e3+5;
bool d[maxn][maxn];
int pt[maxn],ans=0,INF,k=0,n;
int dfn[maxn],low[maxn],In[maxn],st[maxn],tp=0,T=0,cnt=0,vis[maxn];
int turn(char a){
if('0'<=a&&a<='9')return a-'0';
else return 10+a-'A';
}
string s;
void tarjan(int u){
dfn[u]=low[u]=++T;
st[++tp]=u,vis[u]=1;
for(int v=1;v<=n;v++){
if(!d[u][v])continue;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}else if(vis[v])low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u]){
++cnt;
while(1){
int x=st[tp--];
vis[x]=0;
if(cnt==1)pt[++k]=x;
if(x==u)break;
}
}
}
int p[maxn],n1=0,q[maxn],n2=0,inq[maxn],inp[maxn];
bool cmp(int x,int y){
return d[x][y];
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;INF=614*n;
for(int i=1;i<=n;i++){
cin>>s;
for(int j=1;j<=n/4;j++){
int t=turn(s[j-1]);
for(int k=0;k<=3;k++){
d[i][4*j-k]=(t>>k)&1;
}
}
}
// for(int i=1;i<=n;i++){
// for(int j=1;j<=n;j++)cout<<d[i][j]<<" ";
// cout<<endl;
// }
// cout<<endl;
for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i);
ans=(n-k)*(n-k-1)/2*(INF+1);
ans+=(n-k)*k*(INF+1);
// cout<<ans<<endl;
int Max=-1,x=-1;
for(int i=1;i<=k;i++){
for(int j=1;j<=k;j++){
if(d[pt[j]][pt[i]])In[pt[i]]++;
}
// cout<<pt[i]<<" "<<In[pt[i]]<<endl;
if(In[pt[i]]>Max){
Max=In[pt[i]],x=pt[i];
}
}
p[++n1]=x;
// cout<<k<<" "<<x<<endl;
for(int i=1;i<=k;i++){
if(pt[i]==x)continue;
if(d[pt[i]][x])p[++n1]=pt[i];
else q[++n2]=pt[i];
}
sort(p+1,p+n1+1,cmp);sort(q+1,q+n2+1,cmp);
ans+=n1*n2*3;
int now=n2;
for(int i=n1;i>=1;i--){
while(now&&d[q[now]][p[i]])--now;
inq[i]=now+1;
}
now=n1;
for(int i=n2;i>=1;i--){
while(now&&d[p[now]][q[i]])--now;
inp[i]=now+1;
}
for(int i=1;i<=n1;i++){
for(int j=i+1;j<=n1;j++){
if(inq[i]==inq[j])ans+=4;
else ans+=3;
}
}
for(int i=1;i<=n2;i++){
for(int j=i+1;j<=n2;j++){
if(inp[i]==inp[j])ans+=4;
else ans+=3;
}
}
cout<<ans<<endl;
return 0;
}
gym xxxxxx K
给定三个序列,构造竞赛图:x 胜 y 当且仅当 x 在至少两个序列比 y 排在更前面。
多次查询 x 是否能到达 y。
三维偏序求出度,利用 Landau 定理求出强连通分量和链信息。
BB
竞赛图没找到几道例题。
缩点后是链指出了基本的结构;
竞赛图两两连边使得它在局部的结构上也可以具有很强的性质;然后饭圈性给出了极好的递归性。
Landou 定理和上面内容配合不大(?),但是可以把强连通分量变成出度计算。
弦图
弦图定义与判定
定义 1.1
弦是连接环上不相邻的两点的边。
弦图是无向图,满足任意 元环都有至少一个弦。
性质 1.1
弦图的生成子图是弦图。
证明:若不是弦图,则加上剩余的点也不会让这个无弦环有弦。
定义 1.2
对于图 ,若 满足 的生成子图中 不连通,则称 是 的一个点割集。
记 。
性质 1.2
设 是 的一个极小点割集, 在 生成子图的连通块是 ,则 ,。
证明:否则,加上 之后 仍然不连通, 也是 的点割集,从而 不是极小的。
定理 1.1
弦图上任意两点 的任意极小点割集 的生成子图是完全图。
证明:首先认为 。
任取 ,则 ,那么 存在只经过 中点的路径,找到最短的一条。这条长度 。对于 同理。此时我们得到了一个长度 的环。弦不可能在 之间(不连通性质),也不可能在 内部或者和一端和 或者 相连(这样不是最短),那么弦就是 。
因此任意两点 都有边, 是完全图。
定义 1.3
如果 的生成子图是完全图,称 为图的一个单纯点。
性质 1.3
任何弦图都有至少一个单纯点,不是完全图的弦图至少有两个不相邻的单纯点。
证明:
归纳证明。
结论对完全图显然成立。
设 , 是一个极小点割集。设 , 为 生成子图。
如果 是完全图,显然 是一 单纯点。否则,由归纳假设, 有两个不相邻的单纯点;
由于 是完全图, 一定有 单纯点(最多有一个在 )。而 在 的单纯点显然也是在 的单纯点。
同样,在 还有一个。证毕。
定义 1.4
设 为 的排列,若 在 的 的生成子图上是单纯点,称 是 的完美消除序列。
以下的左、先代表下标更小。
定理 1.2
一个无向图具有完美消除序列当且仅当它是弦图。
证明:
如果一个无向图具有完美消除序列,假设其存在长度 的环, 为在这个环上对应 最靠左的一个,则它在环上相邻的两点之间必有边,这就是一个弦。
如果一个无向图是弦图,可以先消除单纯点,再把剩下的点的生成子图(这也是弦图)归纳做。
算法 1.1 最大势算法
上面的构造过程朴素实现是 的,自然无法满足需求。
最大势算法(MCS 算法)可以 求出弦图的完美消除序列,当然可以判定一个图是否是弦图。
流程:
维护一个初始为空的序列;
每次把邻域已加入序列点最多的点加入序列,相同的加任意一个,重复这样的操作直到所有点都被加入序列。
反转此序列。
最终得到的序列就是完美消除序列。
判定弦图,只需尝试求出完美消除序列,对每一个点找出其相邻的且在序列更靠后的点 (按序列位置排序),只需判定 是否和 都有边。
为了证明此算法正确性,我们需要知道两个引理。
以下设 为 在这个序列中的位置。
引理 1.1
若 ,且 ,则 。
证明:显然,必须得有一个补齐贡献。
引理 1.2
一个序列 不可能同时满足以下条件,其中 是一个弦图 的序列得出的:
证明:
根据引理 1.1 和 得到 。
找到最小的 满足 ,则 ,否则得到一个无弦环 ,不符合弦图定义。
若 ,则令当前序列为 ,否则为 。
这样的操作可以无穷进行,而每次 会变大,而此式不超过 ,矛盾。
定理 1.3
以上算法正确求出了弦图的完美消除序列。
证明:
考虑任意 满足 ,若 ,则需要说明 。
如果 , 就是满足引理 1.2 的序列,不存在。
证毕。
以下是判定弦图的实现(SP5446)
// Problem: FISHNET - Fishing Net
// Platform: Luogu
// URL: https://www.luogu.com.cn/problem/SP5446
// Memory Limit: 1 MB
// Time Limit: 288000 ms
// Author:British Union
// Long live UOB and koala
//
// Powered by CP Editor (https://cpeditor.org)
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e3+5;
vector<int> e[maxn],pt[maxn];
int n,m,rnk[maxn]/*alpha*/,p[maxn],num[maxn];
map<pair<int,int>,int> mp;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
while(cin>>n>>m){
if(!n&&!m)break;
for(int i=1;i<=m;i++){
int x,y;cin>>x>>y;
e[x].push_back(y);
e[y].push_back(x);
mp[{x,y}]=mp[{y,x}]=1;
}
int Max=0;
for(int i=1;i<=n;i++)pt[0].push_back(i);
for(int i=n;i>=1;i--){
bool f=0;int x;
while(!f){
for(int j=pt[Max].size()-1;j>=0;j--){
x=pt[Max][j];
if(rnk[x])pt[Max].pop_back();
else{
f=1;
break;
}
}
if(!f)Max--;
}
rnk[x]=i;p[i]=x;
for(auto v:e[x]){
if(rnk[v])continue;
pt[++num[v]].push_back(v);
Max=max(Max,num[v]);
}
}
bool ck=1;
for(int i=n;i>=1;i--){
int x=p[i];
vector<int> V;int Min=0x3f3f3f3f;
for(auto v:e[x]){
if(rnk[v]>i){
V.push_back(v);
Min=min(Min,rnk[v]);
}
}
if(V.size()>1){
x=p[Min];
for(auto u:V){
if(u==x)continue;
if(!mp[{x,u}]){
ck=0;
break;
}
}
}
if(!ck)break;
}
if(ck)cout<<"Perfect"<<endl;
else cout<<"Imperfect"<<endl;
for(int i=0;i<=n;i++){
e[i].clear(),pt[i].clear();
p[i]=rnk[i]=0,num[i]=0;
}
mp.clear();
}
return 0;
}
弦图的应用和性质
定理 2.1
记 。
弦图极大团一定可以表示为 。
证明:
任何团 取其 最小的 ,一定有 。
不等于的话不是极大的。
定理 2.2
弦图色数(染色,边两端点颜色不同)等于最大团大小(记作团数)。
证明:
显然对于任意图,团数 色数。
而我们构造这样的方案:按 从大到小染色,染最小的可以染的颜色。
色数等于 ,等于最大团大小。
定理 2.3
弦图最大独立集数等于最小团覆盖数。
证明:
显然在一般图中前者 后者。
构造方案,按 从小到大能加入就加入。
这样的点对应的 构成了团覆盖。
定理 2.4
弦图的色多项式为
-什么是色多项式 ?
-一个多项式,满足 是用 种颜色染色的方案数。
证明:
按 从大到小考虑,根据乘法原理知道。
定理 2.5
记 为 中 最小的。
为极大团当且仅当 。
证明:
如果 ,一定 。
设 为 中 最大的点。显然有 ,否则可以令 。
定义 2.1
称一个树 为另一个图 的团树,当且仅当:
存在一个从 所有极大团到 的双射 ,使得对于所有点有交的极大团 ,。
定理 2.6
图是弦图当且仅当其存在团树。
(没有证明)
算法 2.1
在每个极大团之间连等于其交大小的边。
求最大生成树,得到的就是一颗团树。
(没有证明)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现