日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

最小树形图+朱刘算法

發布時間:2023/12/15 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 最小树形图+朱刘算法 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

大題上完整的朱、劉算法是由四個大步驟組成的:

1、求最短弧集合E

2、判斷集合E中有沒有有向環,如果有轉步驟3,否則轉4

3、收縮點,把有向環收縮成一個點,并且對圖重新構建,包括邊權值的改變和點的處理,之后再轉步驟1。

4、展開收縮點,求得最小樹形圖。

因為我們ACM一般情況下都是在考察隊最小樹型圖的權值問題,所以一般省略步驟4,對于其環的權值和在中間處理過程中就可以處理完畢。所以我們這里就不多討論第四個點了。

我們分步處理、

1、首先我們先求最短弧集合E,對于當前圖如果有n個點(一個有向環的收縮點算作一個點),我們就要選出n-1個點,確定其入邊的最短邊,由其組成的一個集合我們就叫做最短弧集合E,如果我們枚舉到某一個點的時候,它沒有入邊,那么說明不存在最小樹形圖,所以這個時候算法結束,回到主函數。

?

代碼實現:

for(int i=1; i<=n; i++)if(i!=u&&!flag[i])//u作為圖的根節點,flag【i】為1的情況就是表示這個點在某個有向環里邊,并且他不是這個有向環的代表點(縮點){w[i][i]=INF, pre[i] = i;//首先讓當前點的前驅點為自己。for(int j=1; j<=n; j++)if(!flag[j] && w[j][i]<w[pre[i]][i])//枚舉i的前驅點(從j能夠到i的點),并且求其最短邊,加入集合E中{pre[i] = j;//并且標記當前點的前驅點為j}if(pre[i]==i)return -1;//如果當前枚舉到的點i沒有入邊,那么就不存在最小樹形圖(因為一顆樹是要所有節點都是連通的啊)}

2、然后我們對集合E中的邊進行判斷,判斷是否有有向環。剛剛的代碼實現里邊有一個前驅節點的存儲,所以在這個部分,我們直接一直向前枚舉前驅點即可,如果枚舉的前驅點最終能夠枚舉到根節點,那么這一部分就不存在有向環,否則就存在,對于每一個點都進行向前枚舉即可。

?

int i;for(i=1; i<=n; i++){if(i!=u&&!flag[i]){int j=i, cnt=0;while(j!=u && pre[j]!=i && cnt<=n) j=pre[j], ++cnt;//對于每個節點都找前驅節點,看看能否成環。if(j==u || cnt>n) continue; //最后能找到起點(根)或者是走過的點已經超過了n個,表示沒有有向環break;//表示有有向環}}

?

?

3、如果有有向環呢,我們需要對有向環進行縮點,既然我們是枚舉到節點i的時候發現有有向環,我們不妨把有向環里邊的點都收縮成點i。對于收縮完之后會形成一個新的圖,圖的變化規律是這樣的:


上圖變換成語言描述:如果點u在環內,如果點k在環外,并且從k到u有一條邊map【u】【v】=w,并且在環內還有一點i,使得map【i】【k】=w2,辣么map【k】【收縮點】=w-w2;

基于貪心思想,對于環的收縮點i和另外一點k(也在環內),對于環外一點j,如果map【k】【j】 <map【i】【j】,辣么map【i】【j】=map【k】【j】,因為是有向圖,入收縮點的邊要這樣處理,出收縮點的邊也要這樣處理,對于剛剛三個步驟:收縮點,收縮點處理新圖的邊權值,以及基于貪心思想的最終處理點i的出邊入邊權值,后兩者我們可以合并成一個操作,其分別的代碼實現:

3、收縮點:

int j=i; memset(vis, 0, sizeof(vis)); do{ans += w[pre[j]][j], j=pre[j], vis[j]=flag[j]=true;//對環內的點標記,并且直接對環的權值進行加和記錄,在最后找到最小樹形圖之后就不用展開收縮點了 }while(j!=i); flag[i] = false; // 環縮成了點i,點i仍然存在


4、處理收縮點后形成的圖:

for(int k=1; k<=n; ++k)if(vis[k]) // 在環中點點,剛剛在收縮點的時候,已經把在環中的點進行標記了。 {for(int j=1; j<=n; j++)if(!vis[j]) // 不在環中的點{if(w[i][j] > w[k][j]) w[i][j] = w[k][j];if(w[j][k]<INF && w[j][k]-w[pre[k]][k] < w[j][i])w[j][i] = w[j][k] - w[pre[k]][k];} }

處理完4之后,我們就回到步驟1,繼續找最小弧集E,最后找到了一個沒有環的最小弧集E之后,對于沒有弧的集合E中的所有邊(包括能將收縮點展開的邊)就是我們要求的最小樹形圖的邊集。

因為我們ACM一般求的都是最小樹形圖的權值,所以我們一般不需要展開收縮點,在處理環的時候,直接將其邊權值記錄下來就好,當找到一個沒有環的集合E的時候,對其中的最后邊權值進行加和即可,對于最后這部分的加權,代碼實現:

for(int k=1; k<=n; ++k)if(vis[k]) // 在環中點點,剛剛在收縮點的時候,已經把在環中的點進行標記了。 {for(int j=1 ; j<=n; j++)if(!vis[j]) // 不在環中的點 {if(w[i][j] > w[k][j]) w[i][j] = w[k][j];if(w[j][k]<INF && w[j][k]-w[pre[k]][k] < w[j][i])w[j][i] = w[j][k] - w[pre[k]][k];} } 完整的朱、劉算法代碼實現(沒有展開收縮點的): #include <cstdio> #include <cmath> #include <string> #include <cstring> #include <algorithm> #include <limits> #include <vector> #include <stack> #include <queue> #include <set> #include <map> #define lowbit(x) ( x&(-x) ) #define pi 3.141592653589793 #define e 2.718281828459045 #define INF 0x3f3f3f3f using namespace std; typedef unsigned long long ull; typedef long long ll; const int maxN = 1005; int N, M; ll sum; struct Eddge //存邊 {int u, v;ll val;Eddge(int a=0, int b=0, ll c=0):u(a), v(b), val(c) {} }edge[maxN*maxN]; int pre[maxN], id[maxN], vis[maxN], pos; ll in[maxN]; //最小入邊權,pre[]為其前面的點(該邊的起點) ll Dir_MST(int root, int V, int E) //root是此時的根節點,我們最初的時候將0(萬能節點作為根節點進入),V是點的個數(包括之后要收縮之后點的剩余個數),E是邊的條數(不會改變) {ll ans = 0;while(true) //如果還是可行的話{for(int i=0; i<V; i++) in[i] = INF; //給予每個點進行初始化/* (1)、最短弧集合E0 */for(int i=1; i<=E; i++) //通過這么多條單向邊,確定的是每個點的指向邊的最小權值{int u = edge[i].u, v = edge[i].v;if(edge[i].val < in[v] && u!=v) //頂點v有更小的入邊,記錄下來 更新操作,u!=v是為了確保縮點之后,我們的環將會變成點的形式{pre[v] = u; //節點u指向vin[v] = edge[i].val; //最小入邊if(u == root) pos = i; //這個點就是實際的起點}}/* (2)、檢查E0 */for(int i=0; i<V; i++) //判斷是否存在最小樹形圖{if(i == root) continue; //是根節點,不管if(in[i] == INF) return -1; //除了根節點以外,有點沒有入邊,則根本無法抵達它,說明是獨立的點,一定不能構成樹形圖}/* (3)、收縮圖中的有向環 */int cnt = 0; //接下來要去求環,用以記錄環的個數 找環開始!memset(id, -1, sizeof(id));memset(vis, -1, sizeof(vis));in[root] = 0;for(int i=0; i<V; i++) //標記每個環{ans += in[i]; //加入每個點的入邊(既然是最小入邊,所以肯定符合最小樹形圖的思想)int v = i; //v一開始先從第i個節點進去while(vis[v] != i && id[v] == -1 && v != root) //退出的條件有“形成了一個環,即vis回歸”、“到了一個環,此時就不要管了,因為那邊已經建好環了”、“到了根節點,就是條鏈,不用管了”{vis[v] = i;v = pre[v];}if(v != root && id[v] == -1) //如果v是root就說明是返回到了根節點,是條鏈,沒環;又或者,它已經是進入了對應環的編號了,不需要再跑一趟了{for(int u=pre[v]; u!=v; u=pre[u]) //跑這一圈的環{id[u] = cnt; //標記點u是第幾個環}id[v] = cnt++; //如果再遇到,就是下個點了}}if(cnt == 0) return ans; //無環的情況,就說明已經取到了最優解,直接返回,或者說是環已經收縮到沒有環的情況了for(int i=0; i<V; i++) if(id[i] == -1) id[i] = cnt++; //這些點是環外的點,是鏈上的點,單獨再給他們賦值for(int i=1; i<=E; i++) //準備開始建立新圖 縮點,重新標記{int u = edge[i].u, v = edge[i].v;edge[i].u = id[u]; edge[i].v = id[v]; //建立新圖,以新的點進入if(id[u] != id[v]) edge[i].val -= in[v]; //為了不改變原來的式子,使得展開后還是原來的式子}V = cnt; //之后的點的數目root = id[root]; //新的根節點的序號,因為id[]的改變,所以根節點的序號也改變了}return ans; } int main() {while(scanf("%d%d", &N, &M)!=EOF){sum = 0;for(int i=1; i<=M; i++){scanf("%d%d%lld", &edge[i].u, &edge[i].v, &edge[i].val);edge[i].u++; edge[i].v++; //把‘0’號節點空出來,用以做萬能節點,留作之后用sum += edge[i].val;}sum++; //一定要把sum給擴大,這就意味著,除去萬能節點以外的點鎖構成的圖的權值和得在(sum-1)之內(包含)for(int i=M+1; i<=M+N; i++) //這就是萬能節點了,就是從0這號萬能節點有通往所有其他節點的路,而我們最后的最小樹形圖就是從這個萬能節點出發所能到達的整幅圖{edge[i] = Eddge(0, i-M, sum); //對于所有的N個其他節點都要建有向邊} //此時N+1為總的節點數目,M+N為總的邊數ll ans = Dir_MST(0, N + 1, M+N); //ans代表以超級節點0為根的最小樹形圖的總權值if(ans == -1 || ans - sum >= sum) printf("impossible\n"); //從萬能節點的出度只能是1,所以最后的和必須是小于sum的,而萬能節點的出度就由“ans - sum >= sum”保證else printf("%lld %d\n", ans - sum, pos - M - 1); //pos-M得到的是1~N的情況,所以“-1”的目的就在于這里printf("\n");}return 0; }

?

總結

以上是生活随笔為你收集整理的最小树形图+朱刘算法的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。