第一周学习总结
第一周学习总结
二分图
定义
若
图例:
判定——染色法
用两种颜色对所有顶点染色,要求一条边所连接的 相邻顶点的颜色不同。
染色结束后,如果能实现所有 相邻顶点的颜色都不同,则
如果
二分图的最大匹配
增广路径:若
匈牙利算法
算法过程:不断在图中寻找增广路径,每找到一条连接两个 未盖点 之间的增广路径,就将增广路径上的边 取反(将增广路径上 本属于匹配边的边 从最大匹配中 删除,本 不属于匹配边的边加入 最大匹配)。
由于增广路径的特殊性质,在每次取反操作后,最大匹配的边数会 加1。
证明:由于增广路径的两端点都是未盖点,所以连接两端点的边原先 都不属于最大匹配。所以,增广路径上本不属于最大匹配的边比原属于最大匹配的边数量 多1。
代码实现
#include<stdio.h>
#include<vector>
#include<cstring>
const int N=505;
std::vector<int> v[N];
int n,m,match[N],p[N];
bool vis[N];
bool dfs(int u){
for(auto i:v[u]){
if(!vis[i]){
vis[i]=true;
if(!match[i]||dfs(match[i])){
match[i]=u;
return true;
}
}
}
return false;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1,x;i<=n;i++){
scanf("%d",&x);
int y;
while(x--) scanf("%d",&y),v[i].emplace_back(y);
}
int ans=0;
for(int i=1;i<=n;i++){
std::memset(vis,false,sizeof vis);
ans+=dfs(i);
}
printf("%d",ans);
return 0;
}
时间复杂度:
最大流
由于每个点只会 匹配一个点,所以可以用最大流模型转化求解。
设一个超级源点
在跑最大流之前,我们再 把
最后使用最大流算法(如
注意:最大流在建边时需建一条 流量为
代码实现
#include<stdio.h>//Dinic
#include<algorithm>
#include<queue>
const int N=1e6+5,inf=0x3f3f3f3f;
struct jie{int to,val,nxt;}a[N];
int n,m,e,s,t,head[N],cnt=1,now[N],depth[N];
void add(int x,int y,int z){
cnt++;
a[cnt].to=y;
a[cnt].val=z;
a[cnt].nxt=head[x];
head[x]=cnt;
}
bool bfs(){
for(int i=1;i<=n+m+2;i++) depth[i]=inf;
std::queue<int> q;
depth[s]=0,now[s]=head[s],q.push(s);
while(!q.empty()){
int u=q.front();q.pop();
for(int i=head[u];i;i=a[i].nxt){
if(a[i].val>0&&depth[a[i].to]==inf){
now[a[i].to]=head[a[i].to],depth[a[i].to]=depth[u]+1,q.push(a[i].to);
if(a[i].to==t) return true;
}
}
}
return false;
}
int dfs(int u,int sum){
if(u==t) return sum;
int k,flow=0;
for(int i=now[u];i&&sum>0;i=a[i].nxt){
now[u]=i;
if(a[i].val>0&&depth[a[i].to]==depth[u]+1){
k=dfs(a[i].to,std::min(sum,a[i].val));
if(!k) depth[a[i].to]=inf;
a[i].val-=k,a[i^1].val+=k;
flow+=k,sum-=k;
}
}
return flow;
}
int Dinic(){
int ans=0;
while(bfs()) ans+=dfs(s,inf);
return ans;
}
int main(){
scanf("%d%d%d",&n,&m,&e);
s=n+m+1,t=n+m+2;
for(int i=1;i<=n;i++) add(s,i,1),add(i,s,0);
for(int i=1;i<=m;i++) add(i+n,t,1),add(t,i+n,0);
int x,y;
while(e--) scanf("%d%d",&x,&y),add(x,y+n,1),add(y+n,x,0);
printf("%d",Dinic());
return 0;
}
/*
How can I use Dinic to solve it?
e.g.:
scanf("%d%d%d",&n,&m,&e);
s=n+m+1,t=n+m+2;
for(int i=1;i<=n;i++) add(s,i,1),add(i,s,0);
for(int i=1;i<=m;i++) add(i+n,t,1),add(t,i+n,0);
int x,y;
while(e--) scanf("%d%d",&x,&y),add(x,y+n,1),add(y+n,x,0);
printf("%d",Dinic());
warning:the number of 'cnt' must start of 1
*/
时间复杂度:
关于二分图的扩展定理
好题
[NOI2009]变换序列
分情况进行讨论。
此时
此时
由于
要同时满足字典序最小,则要满足以下条件:
按照上述条件实现即可。
[HNOI2013 DAY1] 消毒
如果这个题是在二维平面上进行的话,那这个题就是一个二分图的 最小点覆盖 模板题。
现在考虑如何转化为三维问题。
由于不会三分图,所以我们考虑将长方体 拍扁,即把三维问题转化为 二维问题。
易知每层有两种选择:
所以,我们枚举每一层是否单独处理。如果是,则直接将答案加
最后将每种情况答案取最小值即为最终答案。
连通性相关
连通图
在无向图中,任意两点都相互可达,则称该图为连通图。
连通分量
无向图的 最大 连通子图称为无向图的连通分量。
最大连通子图
该子图是原图的 连通子图,将原图的任意一个不在该子图中的点加入到该子图后,该子图 不再连通。
有向图的连通性
在有向图中,对于任意一对顶点
有向图的 最大 强连通子图称为该图的 强连通分量(
最大强连通子图
该子图是原图的 强连通子图,将原图中任意一个不在该子图内的点加入到该子图后,该子图 不再强连通。
求强连通分量
传递闭包
时间复杂度为
算法
算法过程:
代码:
#include<stdio.h>
#include<vector>
#include<cstring>
const int N=1e5+5;
std::vector<int> S,v[N],revv[N];
int n,m,sccno[N],cnt;
bool vis[N];
void dfs1(int u){
if(vis[u]) return;
vis[u]=true;
for(auto i:v[u]) dfs1(i);
S.emplace_back(u);
}
void dfs2(int u){
if(sccno[u]) return;
sccno[u]=cnt;
for(auto i:revv[u]) dfs2(i);
}
void kosaraju(){
std::memset(vis,false,sizeof vis);
std::memset(sccno,0,sizeof sccno);
S.clear(),cnt=0;
for(int i=1;i<=n;i++) dfs1(i);
for(auto it=S.rbegin();it!=S.rend();it++) if(!sccno[*it]) cnt++,dfs2(*it);
}
int main(){
while(~scanf("%d%d",&n,&m),n|m){
for(int i=1;i<=n;i++) v[i].clear(),revv[i].clear();
int x,y;
while(m--) scanf("%d%d",&x,&y),v[x].emplace_back(y),revv[y].emplace_back(x);
kosaraju();
puts(cnt==1? "Yes":"No");
}
return 0;
}
时间复杂度:
但由于要做两次
算法
用
用
每次用自己在原图上的儿子节点来更新自身的
在完成自身的更新后,如果
如何记录节点
代码:
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<utility>
#include<cstring>
#define x first
#define y second
const int N=1e4+5;
std::vector<int> v[N];
std::vector<std::pair<int,int> > e;
int n,cnt,low[N],num[N],dfn,sccno[N],s[N],top;
bool vis[N];
void dfs(int u){
s[++top]=u;
low[u]=++dfn,num[u]=dfn;
for(auto i:v[u]){
if(!num[i]) dfs(i),low[u]=std::min(low[u],low[i]);
else if(!sccno[i]) low[u]=std::min(low[u],num[i]);
}
if(low[u]==num[u]){
cnt++;
while(1){
int i=s[top--];
sccno[i]=cnt;
if(u==i) break;
}
}
}
void tarjan(){
std::memset(sccno,0,sizeof sccno);
std::memset(num,0,sizeof num);
std::memset(low,0,sizeof low);
cnt=0,top=0,dfn=0;
for(int i=1;i<=n;i++) if(!num[i]) dfs(i);
}
int main(){
scanf("%d",&n);
for(int i=1,x;i<=n;i++){
for(int j=1;j<=n;j++){
scanf("%d",&x);
if(x) v[i].emplace_back(j),e.emplace_back(std::make_pair(i,j));
}
}
tarjan();
for(auto i:e) if(sccno[i.x]!=sccno[i.y]) vis[sccno[i.y]]=true;
int ans=0;
for(int i=1;i<=cnt;i++) ans+=!vis[i];
printf("%d",ans);
return 0;
}
时间复杂度为
有关无向图的概念
双连通图:在任意两个顶点之间 至少存在
割点:是无向连通图中一个顶点
桥:无向连通图中的一条边,如果删除它,得到的新图 包含两个连通分量。
双连通图:不含割点 的无向连通图。
双连通分量:无向连通图的 最大双连通子图。
点双连通分量:通过 找割点 获得的双连通分量。
边双连通分量:通过 找桥 获得的双连通分量。
求割点
还是需要处理一下每个节点的
点
代码:
#include<stdio.h>
#include<algorithm>
#include<vector>
#include<cstring>
const int N=1e5+5;
std::vector<int> v[N];
int n,m,low[N],num[N],dfn,root;
bool iscut[N];
void dfs(int u,int fa){
low[u]=++dfn,num[u]=dfn;
int child=0;
for(auto i:v[u]){
if(!num[i]){
child++;
dfs(i,u);
low[u]=std::min(low[u],low[i]);
if(u!=root&&low[i]>=num[u]) iscut[u]=true;
}
else if(i!=fa&&num[i]<num[u]) low[u]=std::min(low[u],num[i]);
}
if(u==root&&child>1) iscut[u]=true;
}
void tarjan(){
dfn=0;
std::memset(low,0,sizeof low);
std::memset(num,0,sizeof num);
std::memset(iscut,false,sizeof iscut);
for(int i=1;i<=n;i++) if(!num[i]) root=i,dfs(i,-1);
}
void tarjan(int start){
root=start;
dfs(start,-1);
}
int main(){
scanf("%d%d",&n,&m);
int x,y;
while(m--) scanf("%d%d",&x,&y),v[x].emplace_back(y),v[y].emplace_back(x);
tarjan();
int cnt=0;
for(int i=1;i<=n;i++) cnt+=iscut[i];
printf("%d\n",cnt);
for(int i=1;i<=n;i++) if(iscut[i]) printf("%d ",i);
return 0;
}
求桥
一条在搜索树上的无向边
代码:
void dfs(int u,int fa){
low[u]=++dfn,num[u]=dfn;
// int child=0;
for(auto i:v[u]){
if(!num[i]){
// child++;
dfs(i,u);
low[u]=std::min(low[u],low[i]);
if(low[i]>num[u]) iscut[std::make_pair(u,i)]=true,iscut[std::make_pair(i,u)]=true;
}
else if(i!=fa&&num[i]<num[u]) low[u]=std::min(low[u],num[i]);
}
// if(u==root&&child>1) iscut[u]=true;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App