最小生成树水题大合集

来源:互联网 发布:会声会影x9激活软件 编辑:程序博客网 时间:2024/06/05 15:31

题目一:

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<int> out;double ans;void prim(vector<int> 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<int> 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<int> 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-14using 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这条边是不是在树上,是为1double 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-14using 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-14using 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<node> 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;}