【图论初步】

一.最短路

1.1单源最短路bellman_ford算法

/*
复杂度O(VE)
可以处理负边权图,并且判断是否存在负权回路
vector < edge > g 存边需要先g.clear();然后g.push(from,to,cost)添加边
点的编号从1开始
*/
const int N=2010;
const int INF=0x3f3f3f3f;
struct edge
{
    int from,to,val;
    edge(int x=0,int y=0,int z=0)
    {
        from=x;
        to=y;
        val=z;
    }
};
int dp[N];
vector<edge>g;
bool bellman(int s,int n)//返回false表示有负环回路
{
    for(int i=1; i<=n; i++)dp[i]=INF;
    dp[s]=0;
    for(int i=1; i<n; i++)
    {
        bool falg=true;
        for(int j=0; j<g.size(); j++)
        {
            int u=g[j].from,v=g[j].to,cost=g[j].val;
            if(dp[v]>dp[u]+cost)
            {
                dp[v]=dp[u]+cost;
                falg=false;
            }
        }
        if(falg)return true;
    }
    for(int j=0; j<g.size(); j++)
    {
        int u=g[j].from,v=g[j].to,cost=g[j].val;
        if(dp[v]>dp[u]+cost)return false;
    }
    return true;

}

二、联通分量
2.1 有向图的强连通分量

/*
1.调用时先初始化 用add(x,y)增加边,调用solve()后才能缩点  //已测试三题 AC 代码确保无误  
2.N是点数 M是边数 (点是从1到n),注意M大小。  
3.bcc 记录第i个连通分量的点  belong表示点属于哪个连通分量  sent表示联通分量个数  
4.g是缩点后的树    
5.du记录缩点后的图的每个联通分支出度(可能会有很多重复边)  
*/
const int N=10010;    
const int M=100100;    
vector<int> bcc[N],g[N];   
bool insta[N];  
int head[N],dfn[N],low[N],sta[N],belong[N],du[N];  
int nu,top,tot,sent,n,m;  
struct edge  
{  
    int from,to,nex;  

}e[M];  
void add(int from,int to)  
{  
    e[nu].from=from;  
    e[nu].to=to;  
    e[nu].nex=head[from];  
    head[from]=nu++;  
}  
void tarjan(int x)  
{  
    low[x]=dfn[x]=++tot;  
    sta[top++]=x;  
    insta[x]=true;  
    for(int i=head[x];i!=-1;i=e[i].nex)  
    {  
        int y=e[i].to;  
        if(!dfn[y])  
        {  
            tarjan(y);  
            low[x]=min(low[x],low[y]);  
        }  
        else if(insta[y]&&low[x]>dfn[y])  
            low[x]=dfn[y];  
    }  
    if(low[x]==dfn[x])  
    {  
        int t;  
        sent++;  
        bcc[sent].clear();  
        do  
        {  
            t=sta[--top];  
            insta[t]=false;  
            belong[t]=sent;  
            bcc[sent].push_back(t);  
        }while(t!=x);  
    }  
}  
void init()  
{  
    nu=1;  
    memset(head,-1,sizeof(head));  
    memset(dfn,0,sizeof(dfn));  
    memset(insta,false,sizeof(insta));  
    memset(du,0,sizeof(du));  
}  
void solve()  
{   sent=top=tot=0;  
    for(int i=1;i<=n;i++)  
        if(!dfn[i])  
        tarjan(i);  
}  
void suodian()  
{  
    int x,y;  
    for(int i=1;i<=sent;i++)g[i].clear();  
    for(int i=1;i<nu;i++)  
    {  
        x=belong[e[i].from],y=belong[e[i].to];  
        if(x==y)continue;  
        g[x].push_back(y);  
        du[x]++;  
    }  
}

三、二分图匹配
1).一个二分图中的最大匹配数等于这个图中的最小点覆盖数
König定理是一个二分图中很重要的定理,它的意思是,一个二分图中的最大匹配数等于这个图中的最小点覆盖数。如果你还不知道什么是最小点覆盖,我也在这里说一下:假如选了一个点就相当于覆盖了以它为端点的所有边,你需要选择最少的点来覆盖所有的边。

2).最小路径覆盖=最小路径覆盖=|G|-最大匹配数
在一个N*N的有向图中,路径覆盖就是在图中找一些路经,使之覆盖了图中的所有顶点,
且任何一个顶点有且只有一条路径与之关联;(如果把这些路径中的每条路径从它的起始点走到它的终点,
那么恰好可以经过图中的每个顶点一次且仅一次);如果不考虑图中存在回路,那么每每条路径就是一个弱连通子集.
由上面可以得出:

1.一个单独的顶点是一条路径;
2.如果存在一路径p1,p2,……pk,其中p1 为起点,pk为终点,那么在覆盖图中,顶点p1,p2,……pk不再与其它的
顶点之间存在有向边.
最小路径覆盖就是找出最小的路径条数,使之成为G的一个路径覆盖.
路径覆盖与二分图匹配的关系:最小路径覆盖=|G|-最大匹配数;
3).二分图最大独立集=顶点数-二分图最大匹配
独立集:图中任意两个顶点都不相连的顶点集合。

3.1 匈牙利算法

/*
1.N是点数,下标1-n   
2.用g.push_back建图后,直接调用max_macth()得到最大二分匹配  
3.最终,liky表示与“y轴”上i匹配的值。
4.用DFS不断增加新的匹配  
5.注意手动初始化
*/  
const int N=1000;  
vector<int> g[N];  
int lik[N],n,m;  
bool visy[N];  
bool dfs(int x)  
{  
    for(int i=0;i<g[x].size();i++)  
    {  
        int y=g[x][i];  
        if(visy[y])continue;  
        visy[y]=true;  
        if(lik[y]==-1||dfs(lik[y]))  
        {  
            lik[y]=x;  
            return true;  
        }  
    }  
    return false;  
}  
int max_match()  
{  
    int ret=0;  
    memset(lik,-1,sizeof(lik));  
    for(int i=1;i<=n;i++)  
    {  
        memset(visy,false,sizeof(visy));  
        if(dfs(i))ret++;  
    }  
    return ret;  
}  

3.2.HK算法(优化的匈牙利算法)

/*
1.N表示点数,下标1-n,一般N超过300才用HK算法  
2.直接调用max_macth()得到答案  
3.my和mx存储最终mx和my匹配的点
*/  
const int N=1000;  
vector<int> g[N];  
int mx[N],my[N],dx[N],dy[N],dis,n,m;  
bool vis[N];  
int bfs()  
{  
    dis=INF;  
    memset(dx,-1,sizeof(dx));  
    memset(dy,-1,sizeof(dy));  
    queue<int> que;  
    for(int i=1;i<=n;i++)  
        if(mx[i]==-1)  
    {  
        que.push(i);  
        dx[i]=0;  
    }  
    while(!que.empty())  
    {  
        int x=que.front();que.pop();  
        if(dx[x]>dis)break;  
        for(int i=0;i<g[x].size();i++)  
        {    int y=g[x][i];  
             if(dy[y]!=-1)continue;  

            dy[y]=dx[x]+1;  
            if(my[y]==-1)dis=dy[y];  
            else  
            {  
                dx[my[y]]=dy[y]+1;  
                que.push(my[y]);  
            }  
        }  
    }  
    return dis!=INF;  
}  
int dfs(int x)  
{  
    for(int i=0;i<g[x].size();i++)  
    {int y=g[x][i];  
       if(vis[y]||dy[y]!=dx[x]+1)continue;  
        vis[y]=true;  
        if(my[y]!=-1&&dy[y]==dis)continue;  
        if(my[y]==-1||dfs(my[y]))  
        {  
            my[y]=x;  
            mx[x]=y;  
            return 1;  
        }  
    }  
    return 0;  
}  
int max_macth()  
{  
    int ret=0;  
    memset(my,-1,sizeof(my));  
    memset(mx,-1,sizeof(mx));  
    while(bfs())  
    {  
        memset(vis,false,sizeof(vis));  
        for(int i=1;i<=n;i++)  
            if(mx[i]==-1&&dfs(i))ret++;  
    }  
    return ret;  
}  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值