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

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.node

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.ios

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.git

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.less

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.ide

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.8ui

Sample Output
Case #1: 19.638spa

分析:

此題是一道最小生成樹的模板題,但題目較複雜,咱們採用分步的方法處理問題
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(nlog_2n)\),但不知道因爲什麼緣由會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);
    }
}
相關文章
相關標籤/搜索