模板题---2.2(图论--最小生成树,floyd,二分图)

1.prim算法

(1)朴素版

介绍:
在带权连通图中,V是所有点的集合,U是已经生成最小的树的顶点的集合。
每次都要找到“除了U里面的顶点,还剩余的顶点里面距离U集合最近的一个点,这个点加入U"。
这里抛砖引玉一下,具体看这里,复习非常好:prim算法介绍

题目:prim

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=510;
int map[N][N];
int dist[N];
int st[N];
int n,m;
int sum;

bool prim()
{
	memset(dist,0x3f,sizeof dist);
	dist[1]=0;
	for(int i=0;i<n;i++)
	{
		int t=-1;
		for(int j=1;j<=n;j++)
		{
			if(st[j]==0&&(t==-1||dist[t]>dist[j]))
				t=j;
		}
		st[t]=1;
		sum+=dist[t];
		if(dist[t]==0x3f3f3f3f)
		{
			cout<<"impossible";
			return false;
		}
		for(int j=1;j<=n;j++)
		{	
			if(st[j]==0&&dist[j]>map[t][j])
				dist[j]=map[t][j];
		}
	}
	return true;
}
int main()
{
	cin>>n>>m;
	memset(map,0x3f,sizeof map);
	for(int i=0;i<m;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		map[a][b]=min(map[a][b],c);
		map[b][a]=map[a][b];
	}
	if(prim())
		cout<<sum;
	return 0;
}

(2)堆优化版

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef pair<int,int>PII;
const int N=1e6+10;
int head[N],e[N],ne[N],w[N],idx;
int st[N];
int n,m;
int sum=0;


void add(int a,int b,int c)
{
    e[idx]=b,ne[idx]=head[a],w[idx]=c,head[a]=idx++;
}

void prim()
{
    priority_queue<PII,vector<PII>,greater<PII>>q;  //优先队列
    q.push({0,1});
    int cnt=0;
    while(q.size())
    {
        PII k=q.top();   //堆顶的就是距离生成树集合最近的点
        q.pop();
        int distance=k.first,ver=k.second;
        if(st[ver]==1)continue;  //这句话看起来不需要,其实需要,因为可能堆里面有多个一样的点,但是只有第一个被标记为1且出队。实际上后面这个点都要被标记为1,但是没有标记。理解一下,模拟一下。
        st[ver]=1;
        sum+=distance;
        cnt++;
        for(int i=head[ver];i!=-1;i=ne[i])
        {
           int j=e[i];
           if(st[j]==0)
           {
               q.push({w[i],j}); 
           }
        }
    }
    if(cnt!=n)
        cout<<"impossible";
    else
        cout<<sum;
}
int main()
{
    cin>>n>>m;
    memset(head,-1,sizeof head);
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
    }
    prim();
    return 0;
}

2.floyed算法

题目:添加链接描述
思路:动态规划,从a到b的最短距离等于从a到k和k到b的距离之和。
有点向区间dp。反正这样记忆就好了,第一层for循环必须是k

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=210,INF=0x3f3f3f3f;
int dist[N][N];

int n,m,Q;


void floyd()
{
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                dist[i][j]=min(dist[i][j],dist[i][k]+dist[k][j]);
}


int main()
{
    cin>>n>>m>>Q;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(i==j)
                dist[i][j]=0;
            else
                dist[i][j]=INF;
        }
    for(int i=0;i<m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        dist[a][b]=min(dist[a][b],c);
    }
    floyd();
    while(Q--)
    {
        int a,b;
        cin>>a>>b;
        if(dist[a][b]>INF/2)
            cout<<"impossible"<<endl;
        else cout<<dist[a][b]<<endl;
    }
    
    return 0;
}

3.kruskal算法

思想:排序+并查集
对于枚举的边,如果当前边的两个点都在生成树集合内,则这条边不要,否则就要.
题目:添加链接描述

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=200010;

int p[N];  //并查集
struct node
{
    int a,b,w;
    bool operator <(const node &t)
    {
        return this->w<t.w;
    }
}edge[N*2];

int res=0;
int cnt,n,m;

int find(int x)
{
    if(x!=p[x])
        p[x]=find(p[x]);
    return p[x];
}

void kruskal()
{
    for(int i=1;i<=m;i++)
    {
        int pa=find(edge[i].a);
        int pb=find(edge[i].b);
        if(pa!=pb)
        {
            res+=edge[i].w;
            p[pa]=pb;
            cnt++;
        }
    }
}
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++)p[i]=i;
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        edge[i].a=a,edge[i].b=b,edge[i].w=c;
    }
    sort(edge+1,edge+m+1);
    kruskal();
    if(cnt<n-1)
        cout<<"impossible";
    else
        cout<<res;
    return 0;
}

4.染色法判定二分图

什么是二分图:(引用)
有两顶点集且图中每条边的的两个顶点分别位于两个顶点集中,每个顶点集中没有边直接相连接!

说人话的定义:图中点通过移动能分成左右两部分,左侧的点只和右侧的点相连,右侧的点只和左侧的点相连。

题目:染色法

#include<iostream>
#include<cstring>
using namespace std;
const int N=200010;
int head[N],e[N],ne[N],idx;
int st[N];


int n,m;

void add(int a,int b)
{
	e[idx]=b,ne[idx]=head[a],head[a]=idx++;	
}

bool dfs(int u,int col)
{
	st[u]=col;
	for(int i=head[u];i!=-1;i=ne[i])
	{
		int j=e[i];
		if(st[j]==0)
		{
			if(!dfs(j,3-col))
				return false;
		}
		else
		{
			if(st[j]==col)
				return false;
		}
	}
	return true;
}
int main()
{
	cin>>n>>m;
	memset(head,-1,sizeof head);
	for(int i=0;i<m;i++)
	{	
		int a,b;
		cin>>a>>b;
		add(a,b),add(b,a);
	}
	for(int i=1;i<=n;i++)
	{
		if(st[i]==0)
		{
			if(!dfs(i,1))
			{
				cout<<"No";
				return 0;
			}
		}
	}
	cout<<"Yes";
	return 0;
}

5.匈牙利算法

题目:
二分图的最大匹配

#include<iostream>
#include<cstring>
using namespace std;
const int N = 510 , M = 100010;
int n1,n2,m;
int h[N],ne[M],e[M],idx;
bool st[N];
int match[N];

void add(int a , int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}

void init()
{
    memset(h,-1,sizeof h);
}

int find(int x)
{
    //遍历自己喜欢的女孩
    for(int i = h[x] ; i != -1 ;i = ne[i])
    {
        int j = e[i];
        if(!st[j])//如果在这一轮模拟匹配中,这个女孩尚未被预定
        {
            st[j] = true;//那x就预定这个女孩了
            //如果女孩j没有男朋友,或者她原来的男朋友能够预定其它喜欢的女孩。配对成功
            if(!match[j]||find(match[j]))
            {
                match[j] = x;
                return true;
            }

        }
    }
    //自己中意的全部都被预定了。配对失败。
    return false;
}
int main()
{
    init();
    cin>>n1>>n2>>m;
    while(m--)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);
    }


    int res = 0;
    for(int i = 1; i <= n1 ;i ++)
    {  
         //因为每次模拟匹配的预定情况都是不一样的所以每轮模拟都要初始化
          memset(st,false,sizeof st);
        if(find(i)) 
          res++;
    }  

    cout<<res<<endl;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值