大枝江、Graph Subset Problem

最近最大团的题目出现得越来越多,想到之前模拟赛的大枝江还没订,有点慌。

大枝江

n 个人站成一排,最开始 i 站在 i 处。接下来进行 m 次操作,每次操作让 xixi+1 握手,并交换其位置。求握手关系构成的图的最大团。n,m2×105

突破口:不握手关系按编号顺序构成传递闭包,即 i<j<k,i,j 没握手、j,k 没握手,则 i,k 没握手。

由于最大团 = 补图的最大独立集 = 补图定向后的最小链覆盖,因此答案就是 G=(V=V,E={(u,v)Eu<v}) 的拆点二分图最大匹配。(注:代码为了实现方便,是 u>v 定向的。)

先来介绍一个更快的匈牙利,在 G=(V,E) 的补图上跑时,总复杂度是 O(VEα) 的。具体就是用并查集维护下一个没有 vis 的点。(我好像不是很会分析匈牙利复杂度……)

复制
//事先有:for(int i=1;i<=n;i++)sort(G[i].begin(),G[i].end());
bool dfs(int x){
int k=0;
for(int j=find(1);j<x;j=find(j+1)){
while(k<G[x].size()&&G[x][k]<j)k++;
if(k<G[x].size()&&G[x][k]==j)continue;
fa[j]=j+1;
if(!matr[j]||dfs(matr[j])){
matr[j]=x,matl[x]=j;
return 1;
}
}
return 0;
}

下面考虑怎么实现拆点二分图最大匹配。这个二分图的边数太多了,不能直接跑匈牙利。考虑可以先尽量匹配,即对于每个点,找到其邻居中尚未匹配的任意一个点,匹配上这条边。这与匈牙利最大的不同是,不用每次清空了。这部分代码:

for(int i=1;i<=n;i++)sort(G[i].begin(),G[i].end());
for(int i=1;i<=n+1;i++)fa[i]=i;
for(int i=1;i<=n;i++){
int k=0;
for(int j=find(1);j<i;j=find(j+1)){
while(k<G[i].size()&&G[i][k]<j)k++;
if(k<G[i].size()&&G[i][k]==j)continue;
fa[j]=j+1;
matl[i]=j;
matr[j]=i;
ans++;
break;
}
}

那么剩余没有匹配上的左部点数量是多少呢?由于最开始左部点和右部点都是 n 个,假如现在两边都还剩 k 个匹配不上,说明不存在任意一条从左边的 k 个之一连向右边的 k 个之一的边,而这就有 k×k 条边,那么由于这是在补图上考虑,在原图上就对应了这 k×k 条边全都有,由于原图只有 m 条边,所以 km
于是我们可以对剩下的 O(m) 个点跑匈牙利,这部分复杂度就是 O(m×m) 的。

完整代码:

#include <bits/stdc++.h>
using namespace std;
inline int read(){
int x=0;char ch=getchar();
while(ch<'0'||ch>'9')ch=getchar();
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return x;
}
const int N=2e5+5;
int n,m,ans,aa[N],fa[N],matl[N],matr[N];
vector<int>G[N];
int find(int x){
return x==fa[x]?x:fa[x]=find(fa[x]);
}
bool dfs(int x){
int k=0;
for(int j=find(1);j<x;j=find(j+1)){
while(k<G[x].size()&&G[x][k]<j)k++;
if(k<G[x].size()&&G[x][k]==j)continue;
fa[j]=j+1;
if(!matr[j]||dfs(matr[j])){
matr[j]=x,matl[x]=j;
return 1;
}
}
return 0;
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)aa[i]=i;
for(int i=1,x;i<=m;i++){
x=read();
if(aa[x]>aa[x+1])G[aa[x]].emplace_back(aa[x+1]);
else G[aa[x+1]].emplace_back(aa[x]);
swap(aa[x],aa[x+1]);
}
for(int i=1;i<=n;i++)sort(G[i].begin(),G[i].end());
//max clique = butu max duliji = n - butu chaidian bipartite max matching
for(int i=1;i<=n+1;i++)fa[i]=i;
for(int i=1;i<=n;i++){
int k=0;
for(int j=find(1);j<i;j=find(j+1)){
while(k<G[i].size()&&G[i][k]<j)k++;
if(k<G[i].size()&&G[i][k]==j)continue;
fa[j]=j+1;
matl[i]=j;
matr[j]=i;
ans++;
break;
}
}
for(int i=1;i<=n;i++)if(!matl[i]){
for(int j=1;j<=n+1;j++)fa[j]=j;
ans+=dfs(i);
}
cout<<n-ans<<'\n';
}

CF1439B Graph Subset Problem

给定 n 个点 m 条边的图,你需要输出一个 k 个点的团,或者一个 kcorekcore 的含义是,一个点集,它的导出子图的任意一个点的度数都 k
n,m,k105

看到团肯定想根号,团的大小是不可能超过 2m+1
同时,不论是 k 个点的团还是 kcore,每个点的度数都 k1,所以先不断删 deg<k1 的点直到不存在。
如果我们打算输出的子集包含 deg=k1 的点,那肯定是团了,就枚举每个 deg=k1 的点 O(k2) 判断其 k1 个邻居是否能与它构成团(用unordered_map存邻接矩阵)。然后将这个点删去。
如果还剩下点,那它们肯定能构成 kcore

posted @   pengyule  阅读(39)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示