最小生成树水题大合集

题目一:

hdu - 畅通工程

题意:

中文题省略


思路:

裸最小生成树


代码:

#include "iostream"
#include "cstring"
#include "string"
#include "cmath"
#include "queue"
#include "cstdio"
#include "algorithm"
#include "cctype"
#include "map"
using namespace std;
typedef long long LL;
const int INF = 0x6fffffff;
const int inf = 522133279;
const LL llinf = 1e30;

int G[110][110];
int low[110];
int vis[110];
int N,M;

int prim()
{
    int res=0;

    vis[1]=1;
    low[1]=0;

    for(int i = 2 ; i <= M ; i++)
        low[i] = G[1][i];

    for(int i = 2 ; i <= M ; i++)
    {
        int minc = inf;
        int pos=0;

        for(int i = 1 ; i <= M ; i++)
            if(!vis[i] && low[i] < minc )
            {
                minc = low[i];
                pos = i;
            }

        if(minc == inf)
            return -1;

        vis[pos]=1;
        res += minc;

        for(int i = 1 ; i <= M ; i++)
            if(!vis[i] && low[i] > G[pos][i])
                low[i] = G[pos][i];
    }

    return res;

}

int main()
{
    //freopen("Input.txt" , "r" , stdin);
    //freopen("out.txt","w",stdout);

    while(~scanf("%d%d" , &N,&M) && N)
    {
        memset(vis,0,sizeof(vis));
        memset(G,0x1f,sizeof(G));
        memset(low,0x1f,sizeof(low));

        for(int i = 0 ; i < N ; i++)
        {
            int a,b,w;
            scanf("%d%d%d",&a,&b,&w);

            G[a][b] = G[b][a] = w;
        }

        int res = prim();

        res == -1 ? puts("?") : printf("%d\n" , res);
    }

    return 0;
}

题目二

poj - Highways

题意:

给你一个图的邻接矩阵,求最小生成树,输出的是树中的最大边


思路:

无脑模板题,矩阵都给了,直接改上题的代码


代码:

#include "iostream"
#include "cstring"
#include "string"
#include "cmath"
#include "queue"
#include "cstdio"
#include "algorithm"
#include "cctype"
#include "map"
using namespace std;
typedef long long LL;
const int INF = 0x6fffffff;
const int inf = 522133279;
const LL llinf = 1e30;

int G[550][550];
int low[550];
int vis[550];
int m,n;

int prim()
{
    int res=-1;

    vis[1]=1;
    low[1]=0;

    for(int i = 2 ; i <= n ; i++)
        low[i] = G[1][i];

    for(int i = 2 ; i <= n ; i++)
    {
        int minc = inf;
        int pos=0;

        for(int i = 1 ; i <= n ; i++)
            if(!vis[i] && low[i] < minc )
            {
                minc = low[i];
                pos = i;
            }

        vis[pos]=1;
        res = max(res,minc);

        for(int i = 1 ; i <= n ; i++)
            if(!vis[i] && low[i] > G[pos][i])
                low[i] = G[pos][i];
    }

    return res;

}

int main()
{
    //freopen("Input.txt" , "r" , stdin);
    //freopen("out.txt","w",stdout);

    int t;
    scanf("%d",&t);

    while(t--)
    {
        memset(vis,0,sizeof(vis));
        //memset(G,0x1f,sizeof(G));
        memset(low,0x1f,sizeof(low));

        scanf("%d",&n);
        for(int i = 1 ; i <= n ; i++)
            for(int j = 1 ; j <= n ; j++)
                scanf("%d" , &G[i][j]);

        int res = prim();

        printf("%d\n" , res);
    }

    return 0;
}

题目三

poj - Agri-Net

题意:

和上一题几乎完全相同,求的是最小生成树的总权值


思路:

改上一题代码


代码:

#include "iostream"
#include "cstring"
#include "string"
#include "cmath"
#include "queue"
#include "cstdio"
#include "algorithm"
#include "cctype"
#include "map"
using namespace std;
typedef long long LL;
const int INF = 0x6fffffff;
const int inf = 522133279;
const LL llinf = 1e30;

int G[550][550];
int low[550];
int vis[550];
int m,n;

int prim()
{
    int res=0;

    vis[1]=1;
    low[1]=0;

    for(int i = 2 ; i <= n ; i++)
        low[i] = G[1][i];

    for(int i = 2 ; i <= n ; i++)
    {
        int minc = inf;
        int pos=0;

        for(int i = 1 ; i <= n ; i++)
            if(!vis[i] && low[i] < minc )
            {
                minc = low[i];
                pos = i;
            }

        vis[pos]=1;
        res += minc;

        for(int i = 1 ; i <= n ; i++)
            if(!vis[i] && low[i] > G[pos][i])
                low[i] = G[pos][i];
    }

    return res;

}

int main()
{
    //freopen("Input.txt" , "r" , stdin);
    //freopen("out.txt","w",stdout);

    while(~scanf("%d",&n) && n)
    {
        memset(vis,0,sizeof(vis));
        //memset(G,0x1f,sizeof(G));
        memset(low,0x1f,sizeof(low));


        for(int i = 1 ; i <= n ; i++)
            for(int j = 1 ; j <= n ; j++)
                scanf("%d" , &G[i][j]);

        int res = prim();

        printf("%d\n" , res);
    }

    return 0;
}

题目四:

hdu - 继续畅通工程

题意:
中文题,略

思路:
本题对边有特殊限制,所以就不能用以顶点为操作对象的prim了,改用kruskal,排序法则:已经建好的路权值为0,优先考虑

代码:
#include "iostream"
#include "cstring"
#include "string"
#include "cmath"
#include "queue"
#include "cstdio"
#include "algorithm"
#include "cctype"
#include "map"
using namespace std;
typedef long long LL;
const int INF = 0x6fffffff;
const int inf = 522133279;
const LL llinf = 1e30;

int n;

struct edge
{
    int s;
    int e;
    int w;
    int state;

    bool operator < (const edge& b)const
    {
        if(state == b.state)
            return w < b.w;
        return state > b.state;
    }
}road[10000+100];

int set[110];

int Find(int x)
{
    return set[x] != x ? (set[x] = Find(set[x])) : x;
}

int merge(int x , int y)
{
    return set[Find(x)] = Find(y);
}

int kru()
{
    int m = (n-1)*n/2;

    for(int i = 0 ; i <= n ; i++)
        set[i]=i;

    sort(road,road+m);

    int res=0;
    int cnt=0;

    for(int i = 0 ; i < m ; i++)
    {
        int u = Find(road[i].e);
        int v = Find(road[i].s);

        if(u != v)
        {
            cnt++;
            res += road[i].state ? 0 : road[i].w;
            merge(u,v);
        }

        if(cnt == n-1)
            return res;
    }
}

int main()
{
    //freopen("Input.txt" , "r" , stdin);
    //freopen("out.txt","w",stdout);

    while(~scanf("%d" , &n) && n)
    {
        int cnt=0;
        int t = (n-1)*n/2;

        for(int i = 0 ; i < t ; i++)
            scanf("%d%d%d%d",&road[i].s , &road[i].e , &road[i].w , &road[i].state);

        printf("%d\n" , kru());
    }

    return 0;
}


题目五

hdu - Connect the Cities

题意:
给n个城市,有些城市相连而有些没有,让你在m条可建造的路中选择能够让所有城市相连且消耗最少的方法,输出最小值,不能则输出-1
输入是这样的:
t(数据组数)
n  m  k   总城市数 总可选道路数 总相连城市组数
m行 每行是a到b消耗c的路
k行 每行是:在这组里相连的城市数tt 接下来tt个数显示这些城市的编号

思路:
和上一题很像,可是那k组相连城市直接处理成边的话很可能MLE或TLE,这里引入对已连且边权值最小的点对的简便处理的方法:
就是在输入时直接修改并查集,在kru时,遍历一遍并查集数组,如果遇到祖先不是自己的点,说明点和祖先连接的路权值为0,这条边是肯定要选的,cnt++。
显然这样是不会重复的

代码:
#pragma comment(linker, "/STACK:102400000,102400000")
#include "iostream"
#include "cstring"
#include "string"
#include "cmath"
#include "queue"
#include "cstdio"
#include "algorithm"
#include "cctype"
#include "map"
using namespace std;
typedef long long LL;
const int INF = 0x6fffffff;
const int inf = 522133279;
const LL llinf = 1e30;

int n,m,k;

struct edge
{
    int s;
    int e;
    int w;

    bool operator < (const edge& b)const
    {
        return w < b.w;
    }
}road[30000];

int f[550];

int Find(int x)
{
    return f[x] != x ? (f[x] = Find(f[x])) : x;
}

int merge(int x,int y)
{
    return ((x=Find(x)) != (y=Find(y))) && (f[x]=y);
}

int kru()
{
    sort(road,road+m);

    int res=0;
    int cnt=0;

    for(int i = 1 ; i <= n ; i++)
        cnt += (f[i]!=i);

    for(int i = 0 ; i < m ; i++)
    {
        int u = Find(road[i].e);
        int v = Find(road[i].s);

        if(u != v)
        {
            cnt++;
            res += road[i].w;
            merge(u,v);
        }

        if(cnt == n-1)
            return res;
    }

    return -1;
}

int main()
{
    //freopen("input.txt" , "r" , stdin);
    //freopen("out.txt","w",stdout);

    int t;
    scanf("%d" , &t);

    while(t--)
    {
        scanf("%d%d%d" , &n,&m,&k);

        for(int i = 0 ; i <= n ; i++)
            f[i]=i;

        for(int i = 0; i < m ; i++)
            scanf("%d%d%d",&road[i].s , &road[i].e , &road[i].w);

        for(int i = 0; i < k ; i++)
        {
            int tt;
            scanf("%d" , &tt);

            if(tt > 0)
            {
                int cur;
                scanf("%d" , &cur);

                int ff = Find(cur);

                for(int j = 1 ; j < tt ; j++)
                {
                    scanf("%d" , &cur);
                    f[Find(cur)] = ff;
                }
            }
        }

        printf("%d\n" , kru());
    }

    return 0;
}

题目六

poj - Arctic Network


题意:
题意特难理解。。。。(其实是英语渣)
说是有S个卫星轨道 , P个前哨战,现在要将所有前哨战通过无线通信网络相连,有两种不同的方案:
1,两个前哨战可以通过卫星轨道相互联系,无论他们的距离是多少。
2,两个前哨战通过无线通信联系,这规定他们的距离不能超过D(任意两个前哨战之间的D是相同的)

求的就是连通所有前哨战要规定的最小的D
输入
第一行数据组数
每一组
第一行S,P
P行 , 每行表示一个前哨战的坐标

思路:
连通所有前哨战,显然是求最小生成树
用第一种相连方法,我们可以认为是将权值瞬间变为0,也就是说不用考虑使用卫星轨道的前哨战,贪心思想,肯定是要把轨道给生成树中权值大的边用
用第二种连法,每个权值不能超过D,也就是剩下的最大权值即为D
到这里目的就很明确了,相当于把已经建完的树中的前S大的边去掉,而一共P-1条边,从小到大排序后,应输出第P-S大的边,很简单,cnt到P-S时return就可以了

//#pragma comment(linker, "/STACK:102400000,102400000")
#include "iostream"
#include "cstring"
#include "string"
#include "cmath"
#include "queue"
#include "cstdio"
#include "algorithm"
#include "cctype"
#include "map"
using namespace std;
typedef long long LL;
const int INF = 0x6fffffff;
const int inf = 522133279;
const LL llinf = 1e30;

int cnt;
int s,p;

struct edge
{
    int s;
    int e;
    double w;

    bool operator < (const edge& b)const
    {
        return w < b.w;
    }
}e[125000+100];

struct point
{
    double x;
    double y;
}pp[550];

double dis(point a , point b)
{
    return sqrt((a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
}

int f[550];

int find( int x )
{
    return (f[x] != x) ? (f[x] = find(f[x])) : x;
}

void merger(int a , int b)
{
    int x = find(a);
    int y = find(b);

    if(x!=y)
    f[x] = y;
}

double kru()
{
    int cc=0;
    double res=0;

    for(int i = 0 ; i < cnt ; i++)
    {
        int u = find(e[i].s);
        int v = find(e[i].e);

        if( u != v )
        {
            merger(u , v);
            res = e[i].w;
            cc++;
        }

        if(cc == p-s)
            return res;
    }

    return -1.0;
}

int main()
{
    //freopen("input.txt" , "r" , stdin);
    //freopen("out.txt","w",stdout);

    int t;
    scanf("%d" , &t);

    while(t--)
    {
        scanf("%d%d",&s,&p);

        cnt=0;
        for(int i = 1 ; i <= p ; i++)
        {
            scanf("%lf%lf" , &pp[i].x , &pp[i].y);

            for(int j = 1 ; j < i ; j++)
            {
                e[cnt].s = i;
                e[cnt].e = j;
                e[cnt].w = dis(pp[i],pp[j]);
                cnt++;
            }
        }

        sort(e,e+cnt);

        for(int i = 0 ; i <= p ; i++)
            f[i]=i;

        printf("%.2lf\n" , kru());
    }

    return 0;
}

题目七

poj - Truck History


题意:
这题就是题意难理解。。
说是一个卡车公司,给卡车编号,是一串长为7的小写字母组成的字符串,定义两个卡车间的距离:字符串中相同位置不同字符的字符对数。
然后某卡车可能是另一辆卡车衍生出来的
问的是衍生出所有卡车的最小距离之和

思路:
把两辆卡车间的距离看成权值,卡车看为结点,求的就是联通所有卡车(即保证除初始卡车以外,所有卡车都有爸爸) 的边权总和最小是多少,裸生成树

代码:
//#pragma comment(linker, "/STACK:102400000,102400000")
#include "iostream"
#include "cstring"
#include "string"
#include "cmath"
#include "queue"
#include "cstdio"
#include "algorithm"
#include "cctype"
#include "map"
using namespace std;
typedef long long LL;
const int INF = 0x6fffffff;
const int inf = 522133279;
const LL llinf = 1e30;

int G[2010][2010];
int vis[2010];
int low[2010];
int n;

char s[2010][10];

int calc(char a[] , char b[])
{
    int cnt=0;

    for(int i = 0 ; i < 7 ; i++)
        cnt += (a[i] != b[i]);

    return cnt;
}

int prim()
{
    int res=0;
    vis[1]=1;
    low[1]=0;

    for(int i = 2 ; i <= n ; i++)
        low[i] = G[1][i];

    for(int i = 2 ; i <= n ; i++)
    {
        int minc = inf;
        int pos = 0;

        for(int j = 1 ; j <= n ; j++)
        {
            if(!vis[j] && low[j] < minc)
            {
                pos = j;
                minc = low[j];
            }
        }

        vis[pos]=1;
        res+=minc;

        for(int j = 1 ; j <= n ; j++)
        {
            if(!vis[j] && low[j] > G[pos][j])
                low[j] = G[pos][j];
        }
    }
    return res;
}

int main()
{
    //freopen("input.txt" , "r" , stdin);
    //freopen("out.txt","w",stdout);


    while(~scanf("%d",&n) && n)
    {
        memset(low,0x1f,sizeof(low));
        memset(vis,0,sizeof(vis));
        memset(G , 0x1f , sizeof(G));

        for(int i = 1 ; i <= n ; i++)
        {
            scanf("%s",s[i]);

            for(int j = 1 ; j < i ; j++)
            {
                G[i][j] = G[j][i] = calc(s[i],s[j]);
            }
        }

        printf("The highest possible quality is 1/%d.\n" ,prim());
    }

    return 0;

}


题目八

hdu - Tree

题意:
一堆城市,各自拥有一个幸福值。
对于两个城市,设幸福值分别为a,b 。如果a是素数,或b,或a+b是素数,则他们可以互相连通,权值为min(min(a,b),|a-b|)。
问联通所有城市的最小权值和,如果不能联通则输出-1

思路:
筛法素数,注意prime[0] , prime[1] 置1 , 权值和res设为long long ,其余没什么了,模板题

代码:
//#pragma comment(linker, "/STACK:102400000,102400000")
#include "iostream"
#include "cstring"
#include "string"
#include "cmath"
#include "queue"
#include "cstdio"
#include "algorithm"
#include "cctype"
#include "map"
using namespace std;
typedef long long LL;
const int INF = 0x6fffffff;
const int inf = 522133279;
const LL llinf = 1e30;

int n;
int p[650];
int G[650][650];
int low[650];
int vis[650];
int prime[2000100];

void init()
{
    prime[0]=prime[1]=1;
    
    prime[2]=0;
    for(int i = 2 ; i <= 2000000 ; i++)
        if(!prime[2])
        {
            for(int j = i*2 ; j <= 2000000; j+=i)
                prime[j]=1;
        }
}

int w(int a , int b)
{
    if(!prime[a] || !prime[b] || !prime[a+b])
        return min(min(a,b),abs(a-b));
    return inf;
}

LL prim()
{
    vis[1]=1;
    low[1]=0;
    LL res=0;

    for(int i = 2 ; i<= n ; i++)
        low[i] = G[1][i];

    for(int i = 2 ; i <= n ; i++)
    {
        int minc = inf;
        int pos=0;
        for(int j = 1 ; j <= n ; j++)
        {
            if(!vis[j] && low[j] < minc)
            {
                pos=j;
                minc = low[j];
            }
        }

        vis[pos] = 1;
        res+=minc;

        if(minc == inf)
            return -1;

        for(int j = 1 ; j <= n ; j++)
        {
            if(!vis[j] && low[j] > G[pos][j])
            {
                low[j] = G[pos][j];
            }
        }
    }

    return res;
}

int main()
{
    //freopen("input.txt" , "r" , stdin);
    //freopen("out.txt","w",stdout);
    int t;
    scanf("%d" , &t);

    init();

    while(t--)
    {
        memset(G,0x1f,sizeof(G));
        memset(vis,0,sizeof(vis));
        memset(low,0x1f,sizeof(low));

        scanf("%d" , &n);

        for(int i = 1 ; i <= n ; i++)
        {
            scanf("%d" , p+i);
            for(int j = 1 ; j < i ; j++)
                G[i][j] = G[j][i] = w(p[i],p[j]);
        }

        cout << prim() << endl;
    }

    return 0;

}


题目九

hdu - Fibonacci Tree

题意:给一个无向图的一些边和颜色(1白0黑),求一颗生成树,使得白色边的树为斐波那契数

思路:

这里有个很重要的推论:

用给定的一些边构造一颗边权值为0或1的生成树,那么只要权值q在最小生成树权值和最大生成树权值之间,就一定可以用某些边替换另一些边来达到这个权值q
生成树的构造理解,一定得记住!!

知道了推论这题就很好做了,找两颗生成树就可以了

代码如下:
//#pragma comment(linker, "/STACK:102400000,102400000")
#include "iostream"
#include "cstring"
#include "string"
#include "cmath"
#include "queue"
#include "cstdio"
#include "algorithm"
#include "cctype"
#include "map"
using namespace std;
typedef long long LL;
const int INF = 0x6fffffff;
const int inf = 522133279;
const LL llinf = 1e30;

int fi[1000000];
int n,m;

struct edge
{
    int s;
    int e;
    int w;

    bool operator < (const edge& b)const
    {
        return w > b.w;
    }
}e[100000+100];


void init()
{
    int a=1,b=1,c=2;
    fi[1]=1;
    do
    {
        fi[c]=1;
        a=b;
        b=c;
    }while((c = a+b) < 1000000);
}
int pos;

int f[100000+100];

int find(int x)
{
    return (x == f[x]) ? x : f[x] = find(f[x]);
}

void meger(int x , int y)
{
    x = find(x);
    y = find(y);

    if(x!=y)
        f[x]=y;
}

int kru(int st , int dir)
{
    for(int i = 0 ; i <= n ; i++)
        f[i]=i;

    int cnt=0;
    int res=0;

    for(int i = st ; i>0&&i<=m ; i+=dir)
    {
        int u = find(e[i].s);
        int v = find(e[i].e);

        if(u!=v)
        {
            cnt++;
            res+=e[i].w;
            meger(u,v);
        }

        if(cnt == n-1)
            break;

    }

    if(cnt < n-1)
        return -1;

    return res;
}

int main()
{
    //freopen("input.txt" , "r" , stdin);
    //freopen("out.txt","w",stdout);
    int t;
    scanf("%d" , &t);
    init();

    for(int ka = 1 ; ka <= t ; ka++)
    {

        scanf("%d%d",&n,&m);

        for(int i = 1 ; i <= m ; i++)
            scanf("%d%d%d",&e[i].s,&e[i].e,&e[i].w);

        sort(e+1,e+m+1);

        int r = kru(1,1);
        
        printf("Case #%d: ",ka);

        if(r == -1)
        {
            puts("No");
            continue;
        }

        int l = kru(m,-1);

        int ok=0;
        for(int i = l; i <= r; i++)
            if(fi[i])
            {
                ok=1;
                break;
            }

        ok ? puts("Yes") : puts("No");
    }

    return 0;
}


题目十:

hdu - Minimal Ratio Tree


题意:

输入:
第一行n,m,代表n阶完全图,我们需要找一颗m阶的最小生成树
接下来一行给出n个节点的权值
接下来n行给出这个完全图的邻接矩阵 

输出的是找到的最小生成树的所有顶点,按升序输出

这颗生成树什么要求呢? 就是(边权之和/点权之和)最小

思路:

很有意思的一道题,刚开始我是直接遍历每一个顶点,以每一个顶点为起点prim,当记录的顶点数为m时跳出,更新最小值及相关项,结果wa了,后来想想有可能边权之和最小了但是点权之和不是最大的,而且这样会遗漏情况,直接以除式形式为权值又不好建树,所以改了方法:

由于数据量很小,所以我先dfs获取所有顶点数为m的集合,然后在这个集合中选点建树,由于一个集合中点权之和一定是个定值,所以只要考虑边权之和最小就可以了,而这就是标准的最小生成树。这样枚举完所有情况,也不会出现遗漏了。


代码如下:

//#pragma comment(linker, "/STACK:102400000,102400000")
#include "iostream"
#include "cstring"
#include "string"
#include "cmath"
#include "queue"
#include "cstdio"
#include "algorithm"
#include "cctype"
#include "map"
using namespace std;
typedef long long LL;
const int INF = 0x6fffffff;
const int inf = 522133279;
const LL llinf = 1e30;

int m,n;
int node[20];
int G[20][20];
int vis[20];
double low[20];
vector out;
double ans;

void prim(vector cur)
{
    memset(vis,0,sizeof(vis));
    memset(low,0x1f,sizeof(low));

    vis[cur[0]]=1;
    low[cur[0]]=0;

    double fenzi=0 , fenmu=0;

    for(int i = 0 ; i < cur.size() ; i++)
        fenmu += node[cur[i]];

    for(int i = 0 ; i < m ; i++)
        low[cur[i]] = G[cur[0]][cur[i]];

    for(int i = 1 ; i < m ; i++)
    {
        int minc = inf;
        int pos = 0;
        for(int j = 0 ; j < m ; j++)
        {
            if(!vis[cur[j]] && low[cur[j]] < minc)
            {
                minc = low[cur[j]];
                pos = cur[j];
            }
        }

        vis[pos] = 1;
        fenzi += minc;

        for(int j = 0 ; j < m ; j++)
            if(!vis[cur[j]] && low[cur[j]] > G[pos][cur[j]])
                low[cur[j]] = G[pos][cur[j]];
    }

    if(ans > fenzi/fenmu + 1e-8)
    {
        out = cur;
        ans = fenzi/fenmu;
    }

    return ;
}

void dfs(vector cur , int st)
{
    if(cur.size() == m)
    {
        prim(cur);
        return;
    }

    for(int i = st+1 ; i <= n ; i++)
    {
        cur.push_back(i);
        dfs(cur,i);
        cur.pop_back();
    }
}


int main()
{
    //freopen("input.txt" , "r" , stdin);
    //freopen("out.txt","w",stdout);

    while(~scanf("%d%d" , &n,&m) && (m||n))
    {
        ans = inf;

        for(int i = 1 ; i <= n ; i++)
            scanf("%d" , node+i);

        for(int i = 1 ; i <= n ; i++)
            for(int j = 1 ; j <= n ; j++)
                scanf("%d" , &G[i][j]);

        vector zouqi;
        dfs(zouqi,0);

        sort(out.begin() , out.end());

        for(int i = 0 ; i < out.size() ; i++)
        {
            if(i==0)
                printf("%d" , out[i]);
            else
                printf(" %d" , out[i]);
        }
       puts("");
    }


    return 0;
}


题目十一:

poj - Slim Span

题意:

给一个无向图,求一颗生成树,它的最大边减去最小边的值最小


思路:

暴力kruscal,具体看代码一目了然


代码:

#pragma comment(linker, "/STACK:102400000,102400000")
#include "iostream"
#include "cstring"
#include "algorithm"
#include "cmath"
#include "cstdio"
#include "sstream"
#include "queue"
#include "vector"
#include "string"
#include "stack"
#include "cstdlib"
#include "deque"
#include "fstream"
#include "map"
#define eps 1e-14
using namespace std;
typedef long long LL;
const int MAXN = 1000000+100;
const int INF = 0x6fffffff;
const int inf = 522133279;
const long long LLinf = 1e30;

struct edge
{
    int s;
    int e;
    int w;

    bool operator < (const edge& b)const
    {
        return w < b.w;
    }
}e[10010];

int f[110];
int n,m;
int ans;

int find(int x)
{
    return f[x] == x ? x : f[x] = find(f[x]);
}

void merger(int x , int y)
{
    x = find(x);
    y = find(y);

    if(x != y)
        f[x] = y;
}

void kru(int st)
{
    for(int i = 0 ; i <= n ; i++)
        f[i] = i;

    int cnt=0;
    int minc=inf;
    int maxc = -1;

    for(int i = st ; i < m ; i++)
    {
        int x = find(e[i].s);
        int y = find(e[i].e);

        if(x != y)
        {
            cnt++;
            merger(x,y);
            minc = min(minc , e[i].w);
            maxc = max(maxc , e[i].w);
        }

        if(cnt == n-1)
        {
            ans = min(ans,maxc - minc);
            return ;
        }
    }

}

int main()
{
    //freopen("input.txt" , "r" , stdin);

    while(~scanf("%d%d" , &n,&m) && (n||m))
    {
        ans = inf;

        for(int i = 0 ; i < m ; i++)
            scanf("%d%d%d" , &e[i].s,&e[i].e,&e[i].w);

        sort(e,e+m);

        for(int i = 0 ; i <= m-n+1 ; i++)
            kru(i);

        if(ans == inf)
            ans = -1;
        printf("%d\n" , ans);
    }

    return 0;
}

题目十二:

poj 2031 - Building a Space Station

题意:

输入一行四个数的是一个球的三维坐标加半径,两个球能相互触碰到的话权值为0,否则就要用边去连接这两个球,边的长度是球面之间最小距离


思路:

无脑模板题不解释


代码:

//#pragma comment(linker, "/STACK:102400000,102400000")
#include "iostream"
#include "cstring"
#include "string"
#include "cmath"
#include "queue"
#include "cstdio"
#include "algorithm"
#include "cctype"
#include "vector"
#include "map"
#include "stack"
using namespace std;
typedef long long LL;
const int INF = 0x6fffffff;
const int inf = 522133279;
const LL llinf = 1e30;
const double eps = 1e-10;

int n;

struct ball
{
    double x,y,z;
    double r;
}Ball[110];

double dis(ball a , ball b)
{
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)+(a.z-b.z)*(a.z-b.z));
}
double g[110][110];
int vis[110];
double low[110];

double prim()
{
    memset(vis,0,sizeof(vis));
    memset(low,0x1f,sizeof(low));

    vis[1]=1;
    low[1]=0;
    double res=0;

    for(int i = 1 ; i <= n ; i++)
        low[i] = g[1][i];

    for(int i = 2 ; i <= n ; i++)
    {
        double minc = inf;
        int pos = 0;

        for(int j = 1 ; j <= n ; j++)
            if(!vis[j] && low[j] < minc)
        {
            minc = low[j];
            pos = j;
        }

        vis[pos]=1;
        res += minc;

        for(int j = 1 ; j <= n ; j++)
            if(!vis[j] && low[j] > g[pos][j])
            low[j] = g[pos][j];
    }

    return res;
}



int main()
{
    //freopen("input.txt" , "r" , stdin);
    //freopen("out.txt","w",stdout);

    while(~scanf("%d" , &n) && n)
    {
        memset(g,0x1f,sizeof(g));

        for(int i = 1 ; i <= n ; i++)
        {
            scanf("%lf%lf%lf%lf" , &Ball[i].x , &Ball[i].y , &Ball[i].z , &Ball[i].r);

            for(int j = 1; j < i ; j++)
            {
                double tmpd = dis(Ball[i] , Ball[j]);

                g[i][j] = g[j][i] = tmpd > Ball[i].r + Ball[j].r ? tmpd - (Ball[i].r + Ball[j].r) : 0;
            }

        }
        printf("%.3lf\n" , prim());
    }
    return 0;
}

题目十三:

hdu 4081 - Qin Shi Huang's National Road System(次小生成树,重要)

题意:

给n个城市的坐标和人口,需要将这些城市相互可达,消耗就是两点间距离

徐福能够让一条路的消耗为0,称之为魔法路

秦始皇想让总权值最小,徐福想让魔法路连接的两个城市的总人口最大

于是为了折中,令A = 魔法路连接的两个城市的总人口,B = 总权值-魔法路的原权值(即这条路权值为0之后的总权值)

要求A/B最大


思路:

构造生成树无疑,为了使A/B尽可能大,在A能够达到所有组合的情况下,B要尽量小,所以构造的是最小生成树,总权值为quanzhi

然后枚举所有边,分两种情况:

1,这条边在树上,那么分母就是quanzhi - w;

2,这条边不在树上,那么加上这条边之后一定会出现一个环,这条加上的路反正会变成0我们不管,现在要关注的是这棵树已经不是树了,也就是说

多造了无用边,秦始皇哪有这么笨……所以我们要去掉原树上的一条边。假设加入边连通城市a和b,为了保证连通性,肯定是去掉a-b中的一条边,

仍旧是B最小原则,去掉的就是连接a,b的唯一通路的一条权值最大的边。综上,此情况下B = quanzhi - g[a][b]


为什么要进行步骤2?因为我们需要让A尽可能大,选择的城市可能不是在树上直接连通的

于是发现就是求最小生成树的步骤


代码:

//#pragma comment(linker, "/STACK:102400000,102400000")
#include "iostream"
#include "cstring"
#include "string"
#include "cmath"
#include "queue"
#include "cstdio"
#include "algorithm"
#include "cctype"
#include "vector"
#include "map"
#include "stack"
using namespace std;
typedef long long LL;
const int INF = 0x6fffffff;
const int inf = 522133279;
const LL llinf = 1e30;
const double eps = 1e-10;

int n;
struct City
{
    int x;
    int y;
    int people;
}city[1010];

double g[1010][1010];
int vis[1010];
double low[1010];
int tree[1010][1010];               //tree[i][j] 表示i - j这条边是不是在树上,是为1
double maxedge[1010][1010];         //maxedge[i][j] 生成树上连接i-j的唯一路径中的最大边
int pre[1010];                      //pre[i]表示i点之前的那一点,由构造顺序决定

double dis(City a , City b)
{
    return sqrt(1.0*(a.x-b.x)*(a.x-b.x) + (a.y-b.y)*(a.y-b.y));
}
double prim()
{
    memset(maxedge, 0 , sizeof(maxedge));
    memset(vis,0,sizeof(vis));
    memset(low,0x1f,sizeof(low));
    memset(tree,0,sizeof(tree));
    double res=0;

    vis[1]=1;
    low[1]=0;
    pre[1]=1;

    for(int i = 2 ; i <= n ; i++)
        low[i] = g[1][i],pre[i]=1;

    for(int i = 2 ; i <= n ; i++)
    {
        double minc = inf;
        int pos =0;
        for(int j = 1 ; j <= n ; j++)
            if(!vis[j] && low[j] < minc)
            {
                pos = j;
                minc = low[j];
            }

        vis[pos]=1;
        res += minc;
        maxedge[pre[pos]][pos] = maxedge[pos][pre[pos]] = g[pos][pre[pos]]; //相邻两点只有这一种权值了
        tree[pre[pos]][pos] = tree[pos][pre[pos]] = 1;          //边被加入树中

        for(int j = 1 ; j <= n ; j++)
            if(!vis[j] && low[j] > g[pos][j])
            {
                low[j] = g[pos][j];
                pre[j] = pos;       //在刷新j到生成树的最小距离的同时,也要刷新j的前驱结点
            }
        for(int j = 1 ; j <= n ; j++)
            if(vis[j] && j != pos)      //如果j在树中且不是新加入的点,刷新
                //最大边权就是j到pos前驱这个区间中最大的权值和新加的边的权值的较大一个,这里有点dp的意思
                //注意不要被最小生成树构造法则误导:low[i]是i点到当前生成树的最小距离,同样maxedge也是这么构造起来的,有可能最小的边不能加入生成树,因为没有关联的顶点
                //所以不要简单地认为low[pos]一定是最大的边
                //当然kruskal是严格按照边的大小来的
                maxedge[j][pos] = maxedge[pos][j] = max(maxedge[j][pre[pos]] , low[pos]); //需要说明的是low[i] == maxedge[i][pre[i]],因为只有这条边,没得选择
    }

    return res;
}

int main()
{
    int t;
    scanf("%d" , &t);

    while(t--)
    {
        memset(g,0,sizeof(g));

        scanf("%d" , &n);

        for(int i = 1 ; i <= n ; i++)
        {
            scanf("%d%d%d" , &city[i].x,&city[i].y,&city[i].people);

            for(int j = 1 ; j < i ; j++)
                g[i][j] = g[j][i]= dis(city[i],city[j]);
        }

        double quanzhi = prim();
        double maxc = 0;
        for(int i = 1 ; i <= n ; i++)
            for(int j = 1 ; j <= n ; j++)
            {
                if(i==j) continue;

                if(!tree[i][j])
                    maxc = max(maxc , 1.0*(city[i].people+city[j].people) / (quanzhi - maxedge[i][j]));
                else
                    maxc = max(maxc , 1.0*(city[i].people+city[j].people) / (quanzhi - g[i][j]));
            }

        printf("%.2lf\n" , maxc);
    }

    return 0;
}


题目十四:

poj 1679 - The Unique MST

题意:

给一个图让你判断这个图的最小生成树是否唯一


思路:

做过上一题之后这题就显得很水了,就是一个很裸的次小:

先构造一颗最小生成树,然后用不在树中的边替换某位一路径中的最大段,假设这两短长度相同,那么就找到了另一颗最小生成树


代码:

#pragma comment(linker, "/STACK:102400000,102400000")
#include "iostream"
#include "cstring"
#include "algorithm"
#include "cmath"
#include "cstdio"
#include "sstream"
#include "queue"
#include "vector"
#include "string"
#include "stack"
#include "cstdlib"
#include "deque"
#include "fstream"
#include "map"
#define eps 1e-14
using namespace std;
typedef long long LL;
const int MAXN = 1000000+100;
const int INF = 0x6fffffff;
const int inf = 522133279;
const long long LLinf = 1e30;

int n,m;
int g[110][110];
int low[110];
int vis[110];
int maxedge[110][110];
int tree[110][110];
int pre[110];

int prim()
{
    memset(low,0x1f,sizeof(low));
    memset(vis,0,sizeof(vis));
    memset(maxedge,0,sizeof(maxedge));
    memset(tree,0,sizeof(tree));

    low[1]=0;
    vis[1]=1;
    pre[1]=1;
    int res=0;

    for(int i = 2 ; i <= n ; i++)
        low[i] = g[1][i] , pre[i]=1;

    for(int i = 2 ; i <= n ; i++)
    {
        int minc = inf;
        int pos = 0;
        for(int j = 1 ; j <= n ; j++)
            if(!vis[j] && low[j] < minc)
            {
                minc = low[j];
                pos = j;
            }

        vis[pos]=1;
        res+=minc;
        maxedge[pos][pre[pos]] = maxedge[pre[pos]][pos] = g[pos][pre[pos]];
        tree[pos][pre[pos]] = tree[pre[pos]][pos] = 1;

        for(int j = 1 ; j <= n ; j++)
        {
            if(!vis[j] && low[j]>g[pos][j])
                low[j] = g[pos][j] , pre[j] = pos;
        }

        for(int j = 1 ; j <= n ; j++)
        {
            if(vis[j] && j!=pos)
                maxedge[pos][j] = maxedge[j][pos] = max(maxedge[pre[pos]][j],low[pos]);
        }
    }

    return res;
}

int main()
{
    int t;
    scanf("%d" , &t);

    while(t--)
    {
        memset(g,0x1f,sizeof(g));

        scanf("%d%d" , &n,&m);
        for(int i = 0 ; i < m ; i++)
        {
            int a,b,c;
            scanf("%d%d%d" , &a,&b,&c);
            g[a][b] = g[b][a] = c;
        }

        int ok=1;
        int quanzhi = prim();

        for(int i = 1 ; i <= n ; i++)
        {
            for(int j = 1 ; j < i ; j++)
                //这里用更简单的判断边是否在树上的方法,即通过pre判断i,j是否直接相连,这样就不用设tree数组了
                if(/*!tree[i][j]*/i != pre[j] && j != pre[i] && g[i][j] == maxedge[i][j])
                {
                    ok = 0;
                    break;
                }
            if(!ok)
                break;
        }

        if(ok)
            printf("%d\n" , quanzhi);
        else
            puts("Not Unique!");

    }

    return 0;
}

题目十五:

poj 3026 - Borg Maze(有趣的建图,poj神坑题)


题意:

有一群人要消灭藏在迷宫里的外星人,这群人可以在开始或者找到外星人之后分成数波朝不同方向走,走的路径总和是各个队伍的路径和(当然中途分叉的队伍在分叉前算一次路径),求最小路径总和

思路:

这题要是不能分叉,那就是dfs的问题,但是能分叉的话,显然就是一个最小生成树的问题,难就难在建图,建图我用bfs

用一个数组aline记录字母的标号(id),即顶点编号,为避免重复,每一个顶点(x,y)的索引是(x * 行数 + y)

由于是最小生成树,那就不用管什么起点终点了。哪出发都一样


注意:

为什么说这题是神坑题......因为x y之后还可能出现若干空格!!!

wa了2次,然后看了poj的discuss把getchar()改成while(getchar()!='\n');就过了...................擦


代码:

#pragma comment(linker, "/STACK:102400000,102400000")
#include "iostream"
#include "cstring"
#include "algorithm"
#include "cmath"
#include "cstdio"
#include "sstream"
#include "queue"
#include "vector"
#include "string"
#include "stack"
#include "cstdlib"
#include "deque"
#include "fstream"
#include "map"
#define eps 1e-14
using namespace std;
typedef long long LL;
const int MAXN = 1000000+100;
const int INF = 0x6fffffff;
const int inf = 522133279;
const long long LLinf = 1e30;

char maze[110][110];
int g[110][110];
int low[110];
int vis[110];
int dir[4][2] = {0,1,1,0,-1,0,0,-1};
int aline[3000];

struct node
{
    int x;
    int y;
    int path;

    node(){x=0,y=0,path=0;}

    node(int _x , int _y , int _path)
    {
        x = _x;
        y = _y;
        path = _path;
    }
};

int hang,lie;
int id;

int border(int x , int y)
{
    return (x >= 0 && x < hang) && (y >= 0 && y < lie);
}

void bfs(node s)
{
    int use[51][51];
    memset(use,0,sizeof(use));

    use[s.x][s.y]=1;
    queue que;

    while(!que.empty())
        que.pop();

    que.push(s);
    node tmp,pre;
    int sid = s.x*hang+s.y;

    while(!que.empty())
    {
        pre = que.front();
        que.pop();

        for(int i = 0 ; i < 4 ; i++)
        {
            int x = pre.x + dir[i][0];
            int y = pre.y + dir[i][1];

            if(border(x,y) && !use[x][y] && maze[x][y] != '#')
            {
                use[x][y]=1;

                tmp.x = x;
                tmp.y = y;
                tmp.path = pre.path+1;
                que.push(tmp);

                if(isalpha(maze[x][y]))
                    g[aline[sid]][aline[x*hang+y]] = tmp.path;
            }
        }
    }
}

int prim()
{
    memset(low,0x1f,sizeof(low));
    memset(vis,0,sizeof(vis));
    vis[0]=1;
    low[0]=0;
    int res=0;

    for(int i = 1 ; i < id ; i++)
        low[i] = g[0][i];

    for(int i = 1 ; i < id ; i++)
    {
        int minc = inf;
        int pos=0;
        for(int j = 0 ; j < id ; j++)
        {
            if(!vis[j] && low[j] < minc)
            {
                minc = low[j];
                pos = j;
            }
        }

        vis[pos] = 1;
        res += minc;

        for(int j = 0 ; j < id ; j++)
        {
            if(!vis[j] && low[j] >g[pos][j])
                low[j] = g[pos][j];
        }
    }

    return res;
}

int main()
{
    int t;
    scanf("%d",&t);
    getchar();

    while(t--)
    {
        memset(g,0x1f,sizeof(g));
        memset(aline,0,sizeof(aline));

        scanf("%d%d",&lie,&hang);
        while(getchar()!='\n')
            ;

        id=0;
        for(int i = 0 ; i < hang ; i++)
        {
            gets(maze[i]);
            for(int j = 0 ; maze[i][j] ; j++)
                if(isalpha(maze[i][j]))
                    aline[i*hang+j] = id++;
        }

        for(int i = 0 ; i < hang ; i++)
            for(int j = 0 ; j < lie ; j++)
                if(isalpha(maze[i][j]))
                    bfs(node(i,j,0));

        printf("%d\n" , prim());
    }

    return 0;
}


你可能感兴趣的:(图论,解题报告)