problem:codevs 2822
愛在心中 id:wzh
總耗時:4ms 總記憶體損耗:364kb
演算法:tarjan縮點floyd傳遞閉包;
codevs:
題目描述 description
「每個人都擁有乙個夢,即使彼此不相同,能夠與你分享,無論失敗成功都會感動。愛因為在心中,平凡而不平庸,世界就像迷宮,卻又讓我們此刻相逢our home。」
在愛的國度裡有n個人,在他們的心中都有著乙個愛的名單,上面記載著他所愛的人(不會出現自愛的情況)。愛是具有傳遞性的,即如果a愛b,b愛c,則a也愛c。
如果有這樣一部分人,他們彼此都相愛,則他們就超越了一切的限制,用集體的愛化身成為乙個愛心天使。
現在,我們想知道在這個愛的國度裡會出現多少愛心天使。而且,如果某個愛心天使被其他所有人或愛心天使所愛則請輸出這個愛心天使是由哪些人構成的,否則輸出-1。
輸入描述 input description
第1行,兩個數n、m,代表愛的國度裡有n個人,愛的關係有m條。
第2到第m+1行,每行兩個數a、b,代表a愛b。
輸出描述 output description
第1行,乙個數,代表愛的國度裡有多少愛心天使。
第2行,如果某個愛心天使被其他所有人和愛心天使所愛則請輸出這個愛心天使是由哪些人構成的(從小到大排序),否則輸出-1。
樣例輸入1:
6 7
1 2
2 3
3 2
4 2
4 5
5 6
6 4樣例輸出1:
2 2 3
樣例輸入2:
3 3
1 2
2 1
2 3樣例輸出2:
1 -1
資料範圍:點大約有10000,邊大約有100000
解答:
第一次看見這個題目被分類在spfa中,我不知道是誰分的。如果是spfa的話,那麼我們的思路很簡單:從每個點出發走一遍圖的便利,時間複雜度大概是o(n2),絕對超時,反而不如用floyd傳遞閉包。因為我要從每乙個點出發走向每乙個點。
floyd演算法在《演算法競賽入門經典第二版》p364的floyd演算法中有解釋;
具體的思路參考floyd的證明過程,不難發現因為本題中並沒有權值,所以我們只用把點與點之間的聯通性傳遞就可以了。時間複雜度o(n3)。
但是這個資料用floyd依然會超時,具體會有多少分我沒有嘗試,因為太複雜了(懶)。
那麼我們再來看一下題目。愛心天使,用專業的話來說就是強聯通分量(scc),那麼我們就可以很自然地想到用tarjan找出這個帶環的森林中的環,然後把環縮成點,最後用floyd傳遞閉包,這樣我們就可以優化程式中的一大半時間。因為乙個scc中向外的聯通性是相同的,所以我們把乙個scc當作乙個點來處理就可以了。這裡不懂的同學可以畫個圖來模擬一下。
tarjan的具體原理請看網上講解的**,說明一下我上面陣列名的含義
vis:記錄我這個點有沒有被訪問過,flag:就是記錄的我兩個點的聯通性。
dfn:記錄的是這個點在被前序遍歷的過程中的編號
low:記錄的是當前這個點所在scc中的最小的dfn;
那麼我們很容易得出當dfn等於low的時候這個點就是該scc中最早出現的點。
這兒可能有兩種情況:第一,當前點自己本身就是乙個scc,第二,當前是scc中最早出現的點(祖宗)。
sccno記錄的是當前點的scc編號(對應的是scc_cnt),(可能很多人覺得這一步沒有必要,直接記錄為low的值不就可以了嗎?)(在縮點中有奇效)
num:記錄的是當前scc中有多少個點。
tarjan的大致思路如下:這一段中首先標記當前這個點已經被遍歷了,然後dfs_clock++的含義就是給每乙個點編號,同時把這個點壓到棧。現在棧中的所有的點都是當前這個強連通分量中的點。
然後對這個點的子節點進行遍歷,如果子節點沒有被訪問過,那麼就訪問子節點。同時在dfs結束之後對當前節點【不是子節點】的low值進行更新。【對應第乙個if語句】
如果這個子節點已經被訪問過,但是並沒有被劃入別的強連通分量中,就代表這個點和當前節點在同乙個強連通分量中。這個時候就對我這個節點的low的值進行更新,注意是把我當前節點的的low值和子節點的dfn值進行比較然後取較小的值進行更新。【對應第二個if語句】
然後這個dfs之後所有的強連通分量中的點就已經全部在棧中間了,並且low的值都是當前強連通分量中最小的dfs_clock的值。
我們就要進行編號工作了。
當我當前節點的的dfn等於low值的時候,這個點已經是強連通分量中的最早點了。所以我對在棧中處於該點一上的點進行編號【並且劃分進同乙個強連通分量】。這個時候應該注意這個迴圈當該強聯通分量的點全部彈出結束,而不是把棧中的元素全部彈出來。
這樣我們整個tarjan函式就結束了。下面我來貼**
void tarjan(int u)
else
if(!sccno[p->v])
}if(low[u]==dfn[u])
if(num[scc_cnt]==1) k++;
}}
然後我們就要開始縮點啦啦啦啦。
for(int i=1;i<=n;i++)
for(node *p=head[i];p!=null;p=p->next)
flag[sccno[p->u]][sccno[p->v]]=true;
我們重新的在乙個新的鄰接矩陣中間再一次建立乙個圖,這裡選用鄰接矩陣有兩個原因第一:我們這個時候的資料範圍已經很小了。鄰接矩陣完全可以存下去。第二:鄰接矩陣才可以floyd傳遞閉包。
縮完點之後我們應該傳遞閉包來證明聯通性,輸出滿足的那些點。
for(int k=1;k<=scc_cnt;k++)
for(int i=1;i<=scc_cnt;i++)
for(int j=1;j<=scc_cnt;j++)
if(flag[i][k]&&flag[k][j])
flag[i][j]=true;
codevs 2822 愛在心中
題目描述 每個人都擁有乙個夢,即使彼此不相同,能夠與你分享,無論失敗成功都會感動。愛因為在心中,平凡而不平庸,世界就像迷宮,卻又讓我們此刻相逢our home。在愛的國度裡有n個人,在他們的心中都有著乙個愛的名單,上面記載著他所愛的人 不會出現自愛的情況 愛是具有傳遞性的,即如果a愛b,b愛c,則a...
Codevs 2822 愛在心中
2822 愛在心中 時間限制 1 s 空間限制 128000 kb 傳送門題目等級 鑽石 diamond 題目描述 description 每個人都擁有乙個夢,即使彼此不相同,能夠與你分享,無論失敗成功都會感動。愛因為在心中,平凡而不平庸,世界就像迷宮,卻又讓我們此刻相逢our home。在愛的國度...
codevs2822 愛在心中
題目描述 description 每個人都擁有乙個夢,即使彼此不相同,能夠與你分享,無論失敗成功都會感動。愛因為在心中,平凡而不平庸,世界就像迷宮,卻又讓我們此刻相逢our home。在愛的國度裡有n個人,在他們的心中都有著乙個愛的名單,上面記載著他所愛的人 不會出現自愛的情況 愛是具有傳遞性的,即...