HDU 3228 題解(最小生成樹)(Kruskal)(內有詳細註釋)

題面:

Island Explorer

Time Limit: 6000/3000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1297 Accepted Submission(s): 247

Problem Description
A group of explorers has found a solitary island. They land on the island and explore it along a straight line. They build a lot of campsites while they advance. So the campsites are laid on the line.

Coincidently, another group of explorers land on the island at the same time. They also build several campsites along another straight line. Now the explorers meet at the island and they decide to connect all the campsites with telegraph line so that they can communicate with each other wherever they are.

Simply building segments that connect a campsite to another is quite easy, but the telegraph line is rare. So they decide to connect all the campsites with as less telegraph line as possible. Two campsites are connected if they are directly connected with telegraph line or they are both connected to another campsite.

Input
There are multiple test cases.
The number of the test cases is in the first line of the input.

For each test case, first line contains two integers N and M (0≤N, M≤10000), which N is the number of the campsites of the first group of explorers and M is the number of the campsites of the second group of explorers. And there exist at least one campsite.

The next two lines contain eight integers Ax, Ay, Bx, By, Cx, Cy, Dx, Dy. Their absolute values are less than 1000. The integers are the coordinates of four points A, B, C and D. The exploring path of the first group is begin with the first point A and end with the second point B, and the path of the second group is from the third point C to the fourth point D. Every pair of points is distinct.

The last two lines of the test case contain N and M real numbers; they indicate the positions of the campsites. Suppose the i-th real number in the first line is t. It means the x-coordinate of the i-th campsite is Ax * t + Bx * (1-t), and the y-coordinate is Ay * t + By * (1-t). Equally, the campsite on the second straight line is C * t + D * (1-t). You can assume that there are at most four digits in the decimal part, and the numbers are always between 0 and 1.

Output
For each test case, output contains only a real number rounded to 0.001.

Sample Input
1
4 4
0 0 10 10
0 10 10 0
0.1 0.3 0.6 0.8
0.1 0.3 0.6 0.8

Sample Output
Case #1: 19.638

分析:

此題是一道最小生成樹的模板題,但題目較複雜,我們採用分步的方法處理問題
1.處理點
爲了處理點的方便,我們可以編寫結構體,使點的處理更簡潔。還有一點細節,一條直線上的同一個位置可能會有多個營地,所以要判重

struct point {
    double x;//x座標
    double y;//y座標
    int id;//點的編號(之後要用)
} p[MAXN],q[MAXN];
point make_point(double x,double y,int id) {//初始化
    point tmp;
    tmp.x=x;
    tmp.y=y;
    tmp.id=id;
    return tmp;
}
void input(point* a) {//輸入
    scanf("%lf%lf",&(a->x),&(a->y));
}
double getdis(point a,point b) {//兩點之間距離公式
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

2.建圖
時間方面:如果我們把每個點和其他所有點都連起來,時間複雜度是O(n+m)2) ,肯定是不行的,因此我們先將同一條直線上的每一個點連起來,然後對於一條直線上的點,三分出距離另一條直線最近的兩個點進行連邊。爲了防止特殊情況,也把這兩個點旁邊的兩個點連接起來。

int l,r;
l=0;
r=m-1;
while(r-l>1) {//三分過程
    int mid1=(r+l)/2;
    int mid2=(mid1+r)/2;
    if(getdis(p[i],q[mid1])>getdis(p[i],q[mid2])) l=mid1;
    else r=mid2;
}

空間方面:用鄰接矩陣的話空間會超出限制,於是採用鄰接表進行存儲

struct edge_table {
    int from;//起點
    int to;//終點
    double value;//長度
} edge[MAXN*32];
int edge_cnt=0;//數邊的條數
void add_edge(int u,int v,double w) {
    edge[++edge_cnt].from=u;
    edge[edge_cnt].to=v;
    edge[edge_cnt].value=w;
}

3.最小生成樹
筆者一開始使用的是堆優化的prim算法,理論上時間複雜度應爲O(nlog2n) ,但不知道由於什麼原因會TLE,使用Kruskal算法則可AC
TLE代碼:

struct edge_table {
    int from;
    int to;
    //int next;
    double value;
} edge[MAXN*32];
int head[MAXN+MAXN];
int edge_cnt=0;
void add_edge(int u,int v,double w) {
    edge[++edge_cnt].from=u;
    edge[edge_cnt].to=v;
    edge[edge_cnt].value=w;
    edge[edge_cnt].next=head[u];
    head[u]=edge_cnt;
}

struct heap_node{//建堆
    int id;
    double value;
    friend bool operator <(heap_node a,heap_node b){
        return a.value>b.value;
    }
};

double key[MAXN+MAXN];
int used[MAXN+MAXN];
double prim(){
    double ans=0;
    int tot=0;
    memset(key,0x7f,sizeof(key));
    memset(used,0,sizeof(used));
    priority_queue<heap_node>heap;
    heap_node now,nex;
    now.id=1;
    now.value=key[1]=0;
    heap.push(now);
    while(!heap.empty()){
        now=heap.top();
        heap.pop();
        int u=now.id;
        if(now.value!=key[u]) continue;
        used[u]=1;
        ans+=key[u];
        tot++;
        for(int i=head[u];i;i=edge[i].next){
            int v=edge[i].to;
            if(used[v]==0&&key[v]>edge[i].value){
                key[v]=edge[i].value;
                nex.value=key[v];
                nex.id=v;
                heap.push(nex);
            }
        }
    }
    if(tot<n+m) ans=-1;
    return ans;
}

這裏寫圖片描述

AC代碼:

int father[MAXN];//建立並查集
int find(int x) {//並查集的查找函數
    if(father[x]!=x) father[x]=find(father[x]);//路徑壓縮
    return father[x];
}
int comp(edge_table a,edge_table b) {//按邊的長度從小到大排序
    return a.value<b.value;
}
double kruskal() {
    for(int i=0; i<=n+m; i++) father[i]=i;//初始化並查集,讓每一個點自成一個獨立的連通分量
    double sum=0;
    sort(edge+1,edge+edge_cnt+1,comp);//按邊的長度從小到大排序
    for(int i=1; i<=edge_cnt; i++) {
        int tx=find(edge[i].from);
        int ty=find(edge[i].to);
        if(tx!=ty) {//如果兩個點在兩個不同的連通分量
            father[tx]=ty;//合併兩個連通分量
            sum+=edge[i].value;//將邊加入最小生成樹
        }
    }
    return sum;
}

代碼:

全部代碼如下:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define MAXN 50005
using namespace std;
struct point {
    double x;//x座標
    double y;//y座標
    int id;//點的編號(之後要用)
} p[MAXN],q[MAXN];
point make_point(double x,double y,int id) {//初始化
    point tmp;
    tmp.x=x;
    tmp.y=y;
    tmp.id=id;
    return tmp;
}
void input(point* a) {//輸入
    scanf("%lf%lf",&(a->x),&(a->y));
}
double getdis(point a,point b) {//兩點之間距離公式
    return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}

struct edge_table {
    int from;//起點
    int to;//終點
    double value;//長度
} edge[MAXN*32];
int edge_cnt=0;//數邊的條數
void add_edge(int u,int v,double w) {
    edge[++edge_cnt].from=u;
    edge[edge_cnt].to=v;
    edge[edge_cnt].value=w;
}
int n,m;
double t1[MAXN],t2[MAXN];
void delete_same() {//判重函數
    sort(t1,t1+n);
    sort(t2,t2+m);
    int ptr=0;
    for(int i=0; i<n; i++) { //判重過程,可在紙上模擬,方便理解
        if(i==n-1||t1[i]!=t1[i+1]) t1[ptr++]=t1[i];
    }
    n=ptr;//將n重置爲判重後點的數目
    ptr=0;
    for(int i=0; i<m; i++) {
        if(i==m-1||t2[i]!=t2[i+1]) t2[ptr++]=t2[i];
    }
    m=ptr;
}

int father[MAXN];//建立並查集
int find(int x) {//並查集的查找函數
    if(father[x]!=x) father[x]=find(father[x]);//路徑壓縮
    return father[x];
}
int comp(edge_table a,edge_table b) {//按邊的長度從小到大排序
    return a.value<b.value;
}
double kruskal() {
    for(int i=0; i<=n+m; i++) father[i]=i;//初始化並查集,讓每一個點自成一個獨立的連通分量
    double sum=0;
    sort(edge+1,edge+edge_cnt+1,comp);//按邊的長度從小到大排序
    for(int i=1; i<=edge_cnt; i++) {
        int tx=find(edge[i].from);
        int ty=find(edge[i].to);
        if(tx!=ty) {//如果兩個點在兩個不同的連通分量
            father[tx]=ty;//合併兩個連通分量
            sum+=edge[i].value;//將邊加入最小生成樹
        }
    }
    return sum;
}
int main() {
    point a,b,c,d;
    int cnt;
    scanf("%d",&cnt);
    for(int cas=1; cas<=cnt; cas++) {
        int i;
        scanf("%d %d",&n,&m);
        input(&a);
        input(&b);
        input(&c);
        input(&d);
        for(i=0; i<n; i++) scanf("%lf",&t1[i]);
        for(i=0; i<m; i++) scanf("%lf",&t2[i]);
        delete_same();
        for(i=0; i<n; i++) {//將點加入
            p[i]=make_point(a.x*t1[i]+b.x*(1-t1[i]),a.y*t1[i]+b.y*(1-t1[i]),i+1);
        }
        for(i = 0 ; i < m ; i++) {
            q[i]=make_point(c.x*t2[i]+d.x*(1-t2[i]),c.y*t2[i]+d.y*(1-t2[i]),i+n+1);
        }

        edge_cnt=0;
        double sum1=0,sum2=0;
        for(i=0; i<n-1; i++) {//將每條直線上的點連起來
            double l=getdis(p[i],p[i+1]);
            add_edge(p[i].id,p[i+1].id,l);//由於是無向圖,每條邊存兩遍
            add_edge(p[i+1].id,p[i].id,l);
            sum1+=l;
        }
        for(i=0; i<m-1; i++) {
            double l=getdis(q[i],q[i+1]);
            add_edge(q[i].id,q[i+1].id,l);
            add_edge(q[i+1].id,q[i].id,l);
            sum2+=l;
        }
        if(n==0||m==0) {//如果只有一條直線的特判
            printf("Case #%d: %.3lf\n",cas,sum1+sum2);
            continue;
        }

        for(int i=0; i<n; i++) {
            int l,r;
            l=0;
            r=m-1;
            while(r-l>1) {//三分過程
                int mid1=(r+l)/2;
                int mid2=(mid1+r)/2;
                if(getdis(p[i],q[mid1])>getdis(p[i],q[mid2])) l=mid1;
                else r=mid2;
            }
            add_edge(p[i].id,q[l].id,getdis(p[i],q[l]));//將距離最短的兩條邊加入鄰接表
            add_edge(q[l].id,p[i].id,getdis(p[i],q[l]));
            add_edge(p[i].id,q[r].id,getdis(p[i],q[r]));
            add_edge(q[r].id,p[i].id,getdis(p[i],q[r]));
            if(l-1>=0) {//將距離第二短的兩條邊加入鄰接表
                add_edge(p[i].id,q[l-1].id,getdis(p[i],q[l-1]));
                add_edge(q[l-1].id,p[i].id,getdis(p[i],q[l-1]));
            }
            if(r+1<=m-1) {
                add_edge(p[i].id,q[r+1].id,getdis(p[i],q[r+1]));
                add_edge(q[r+1].id,p[i].id,getdis(p[i],q[r+1]));
            }

        }
        double sum=kruskal();
        printf("Case #%d: %.3lf\n",cas,sum);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章