寒假笔记·最短路与最小生成树

Floyd

例题:P2910 [USACO08OPEN]寻宝之路Clear And Present Danger

原题地址

题目描述

Farmer John is on a boat seeking fabled treasure on one of the N (1 <= N <= 100) islands conveniently labeled 1…N in the Cowribbean Sea.

The treasure map tells him that he must travel through a certain sequence A_1, A_2, …, A_M of M (2 <= M <= 10,000) islands, starting on island 1 and ending on island N before the treasure will appear to him. He can visit these and other islands out of order and even more than once, but his trip must include the A_i sequence in the order specified by the map.

FJ wants to avoid pirates and knows the pirate-danger rating (0 <= danger <= 100,000) between each pair of islands. The total danger rating of his mission is the sum of the danger ratings of all the paths he traverses.

Help Farmer John find the least dangerous route to the treasure that satisfies the treasure map’s requirement.
输入格式:

  • Line 1: Two space-separated integers: N and M

  • Lines 2…M+1: Line i+1 describes the i_th island FJ must visit with a single integer: A_i

  • Lines M+2…N+M+1: Line i+M+1 contains N space-separated integers that are the respective danger rating of the path between island i and islands 1, 2, …, and N, respectively. The ith integer is always zero.

输出格式:

  • Line 1: The minimum danger that Farmer John can encounter while obtaining the treasure.
    输入输出样例

输入样例#1:
3 4
1
2
1
3
0 5 1
5 0 2
1 2 0
输出样例#1:
7
说明

There are 3 islands and the treasure map requires Farmer John to visit a sequence of 4 islands in order: island 1, island 2, island 1 again, and finally island 3. The danger ratings of the paths are given: the paths (1, 2); (2, 3); (3, 1) and the reverse paths have danger ratings of 5, 2, and 1, respectively.

He can get the treasure with a total danger of 7 by traveling in the sequence of islands 1, 3, 2, 3, 1, and 3. The cow map’s requirement (1, 2, 1, and 3) is satisfied by this route. We avoid the path between islands 1 and 2 because it has a large danger rating.
代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<cmath>
#include<iostream>
using namespace std;
int ss[110][110],qq[100010];
int main()
{
    int i,j,k,n,m,ans=0;
    scanf("%d%d",&n,&m);
    for(i=1;i<=m;i++)
        scanf("%d",&qq[i]);
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            scanf("%d",&ss[i][j]);
    for(k=1;k<=n;k++)//**k必须置于最外层循环**
        for(i=1;i<=n;i++)
            for(j=1;j<=n;j++)
            {
                if(ss[i][j]>ss[i][k]+ss[k][j])
                    ss[i][j]=ss[i][k]+ss[k][j];
            }
    for(i=2;i<=m;i++)
        ans=ans+ss[qq[i-1]][qq[i]];
    printf("%d\n",ans);
    return 0;
}

隐藏的Floyd

例题:P2419 [USACO08JAN]牛大赛Cow Contest

原题地址

题目描述

FJ的N(1 <= N <= 100)头奶牛们最近参加了场程序设计竞赛:)。在赛场上,奶牛们按1…N依次编号。每头奶牛的编程能力不尽相同,并且没有哪两头奶牛的水平不相上下,也就是说,奶牛们的编程能力有明确的排名。 整个比赛被分成了若干轮,每一轮是两头指定编号的奶牛的对决。如果编号为A的奶牛的编程能力强于编号为B的奶牛(1 <= A <= N; 1 <= B <= N; A != B) ,那么她们的对决中,编号为A的奶牛总是能胜出。 FJ想知道奶牛们编程能力的具体排名,于是他找来了奶牛们所有 M(1 <= M <= 4,500)轮比赛的结果,希望你能根据这些信息,推断出尽可能多的奶牛的编程能力排名。比赛结果保证不会自相矛盾。

输入输出格式

输入格式:
第1行: 2个用空格隔开的整数:N 和 M

第2…M+1行: 每行为2个用空格隔开的整数A、B,描述了参加某一轮比赛的奶 牛的编号,以及结果(编号为A,即为每行的第一个数的奶牛为 胜者)

输出格式:
第1行: 输出1个整数,表示排名可以确定的奶牛的数目

输入输出样例

输入样例#1:
5 5
4 3
4 2
3 2
1 2
2 5
输出样例#1:
2
说明

输出说明:

编号为2的奶牛输给了编号为1、3、4的奶牛,也就是说她的水平比这3头奶

牛都差。而编号为5的奶牛又输在了她的手下,也就是说,她的水平比编号为5的

奶牛强一些。于是,编号为2的奶牛的排名必然为第4,编号为5的奶牛的水平必

然最差。其他3头奶牛的排名仍无法确定。

代码:
floyd不仅能求任意两点的最短路,还能求一个点能否到另一个点。

f[i][j]=f[i][j]|(f[i][k]&f[k][j])表示i能否走到j,即要么一开始i能到j,要么i能到k,k再能到j。

那么这里表示的是i能否赢j。用floyd求出每个点与个点的关系,只要这个点和其他

n-1个点的关系都确定了,就能确定他的排名。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<string>
using namespace std;
int a,b,n,m,f[101][101],ans;
int main()
{
    scanf("%d%d",&n,&m);
    memset(f,0,sizeof(f));
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&a,&b);
        f[a][b]=1;
    }
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
              f[i][j]=f[i][j]|f[i][k]&f[k][j];
    for(int i=1;i<=n;i++)
    {
        int gg=1;
        for(int j=1;j<=n;j++)
        if(i==j)continue;else 
         gg=gg&(f[i][j]|f[j][i]);
         ans+=gg;
    }
    printf("%d\n",ans);
    return 0;
}

最小生成树

例题:P3366 【模板】最小生成树

原题地址

题目描述

如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz

输入输出格式

输入格式:
第一行包含两个整数N、M,表示该图共有N个结点和M条无向边。(N<=5000,M<=200000)

接下来M行每行包含三个整数Xi、Yi、Zi,表示有一条长度为Zi的无向边连接结点Xi、Yi

输出格式:
输出包含一个数,即最小生成树的各边的长度之和;如果该图不连通则输出orz

输入输出样例

输入样例#1:
4 5
1 2 2
1 3 2
1 4 3
2 3 4
3 4 3
输出样例#1:
7
说明

时空限制:1000ms,128M

数据规模:

对于20%的数据:N<=5,M<=20

对于40%的数据:N<=50,M<=2500

对于70%的数据:N<=500,M<=10000

对于100%的数据:N<=5000,M<=200000
代码:
Prim和最短路中的dijkstra很像,由于速度问题,所以这里我用链式前向星存图。Prim的思想是将任意节点作为根,再找出与之相邻的所有边(用一遍循环即可),再将新节点更新并以此节点作为根继续搜,维护一个数组:dis,作用为已用点到未用点的最短距离。

证明:Prim算法之所以是正确的,主要基于一个判断:对于任意一个顶点v,连接到该顶点的所有边中的一条最短边(v, vj)必然属于最小生成树(即任意一个属于最小生成树的连通子图,从外部连接到该连通子图的所有边中的一条最短边必然属于最小生成树)

具体算法流程图解如下:

在这里插入图片描述

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
using namespace std;
int qq[1010][1010],mi[1010];
int n,ans=0;
void pri(int x)
{
	int i,j,min,k;
	for(i=1;i<=n;i++)
	{
		mi[i]=qq[x][i];
	}
	for(i=1;i<n;i++)//每次找距联通块最短的点
	{
		min=99999999;
		for(j=1;j<=n;j++)
		{
			if(mi[j]&&mi[j]<min)
			{
				min=mi[j];
				k=j;
			}
		}
		mi[k]=0;
		ans+=min;
		for(j=1;j<=n;j++)//滚动数组
		{
			if(qq[k][j]<mi[j]&&qq[k][j]) mi[j]=qq[k][j];
		}
	}
}
int main()
{
	int i,j;
	memset(qq,0,sizeof(qq));
	scanf("%d",&n);
	for(i=1;i<=n;i++)
		for(j=1;j<=n;j++)
			scanf("%d",&qq[i][j]);
	pri(1);
	printf("%d\n",ans);
    return 0;
}

Kruskal:

Kruskal算法的思想比Prin好理解一些。先把边按照权值进行排序,用贪心的思想优先选取权值较小的边,并依次连接,若出现环则跳过此边(用并查集来判断是否存在环)继续搜,直到已经使用的边的数量比总点数少一即可。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<string>
using namespace std;
long long int n,m,ans=0,p=0;
long long int fa[100010];
struct nod
{
	long long int a,b,c;
}qq[2000020];
bool cmp(nod u,nod v)
{
	if(u.c!=v.c) return u.c<v.c;
	else return u.a>v.a;
}
long long int father(long long int x)//并查集
{
	if(fa[x]==x) return x;
	else return fa[x]=father(fa[x]);
}
void kru()
{
	long long int i;
	for(i=1;i<=m;i++)
	{
		if(father(qq[i].a)!=father(qq[i].b))
		{
			ans+=qq[i].c;
			fa[father(qq[i].a)]=father(qq[i].b);
			p++;
			if(p==n-1) return;
		}
	}
}
int main()
{
	long long int i;
	scanf("%lld%lld",&n,&m);
	for(i=1;i<=m;i++)
		scanf("%lld%lld%lld",&qq[i].a,&qq[i].b,&qq[i].c);
	sort(qq+1,qq+m+1,cmp);//排序
	for(i=1;i<=n;i++)
		fa[i]=i;
	kru();
	printf("%lld\n",ans);
    return 0;
}

最小生成树运用

例题:P1194 买礼物

原题地址

题目描述
又到了一年一度的明明生日了,明明想要买B样东西,巧的是,这B样东西价格都是A元。

但是,商店老板说最近有促销活动,也就是:

如果你买了第I样东西,再买第J样,那么就可以只花K_{I,J}元,更巧的是,K_{I,J}竟然等于K_{J,I} 。

现在明明想知道,他最少要花多少钱。

输入输出格式
输入格式:
第一行两个整数,A,B。

接下来B行,每行B个数,第I行第J个为K_{I,J}。

我们保证K_{I,J}=K_{J,I},并且K_{I,I}=0

特别的,如果K_{I,J}=0那么表示这两样东西之间不会导致优惠。

输出格式:
一个整数,为最小要花的钱数。

输入输出样例

输入样例#1:
1 1
0
输出样例#1:
1

输入样例#2:
3 3
0 2 4
2 0 2
4 2 0
输出样例#2:
7
说明
样例解释2

先买第2样东西,花费3元,接下来因为优惠,买1,3样都只要2元,共7元。

(同时满足多个“优惠”的时候,聪明的明明当然不会选择用4元买剩下那件,而选择用2元。)

数据规模

对于30%的数据,1≤B≤10。
对于%的数据,1≤B≤500,0≤A,K {I,J}≤1000。

代码:
第i件物品对j有优惠的话就建边,然后从0向各点连边权为a的边,然后跑一边kruskal就OK了。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<string>
#include<map>
#include<vector>
using namespace std;
struct node
{
        int u,v,w;
}e[250000];
int a,b,k,tot=1,ans,f[555];
bool cmp(node x,node y)
{
        return x.w<y.w;
}
int find(int x)
{
        if(f[x]==x) return x;
        return f[x]=find(f[x]);
}
void hb(int x,int y)
{
        int xx=find(x);
        int yy=find(y);
        if(xx!=yy) f[xx]=yy;
}
void build(int x,int y,int z)
{
        k++;
        e[k].u=x;
        e[k].v=y;
        e[k].w=z;
}
void kruskal()
{
        int j=1;
        while(j<=k&&tot<=b)
    {
            if(find(e[j].u)!=find(e[j].v))
            {
                    tot++;
                    ans+=e[j].w;
                    hb(e[j].u,e[j].v);
            }
            j++;
        }
}
int main()
{
        scanf("%d%d",&a,&b);
        for(int i=1;i<=b;i++)
        {
                for(int j=1;j<=b;j++)
                {
                        int x;
                        scanf("%d",&x);
                        if(i<j&&x!=0) build(i,j,x);
                }
        }
        for(int i=1;i<=b;i++) build(0,i,a);
        for(int i=0;i<=b;i++) f[i]=i;
        sort(e+1,e+k+1,cmp);
        kruskal();
        printf("%d\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值