/*
雙連通 點雙連通 邊雙連通 奇圈 經典題 好題
TLE + WA 了 N次,終於AC了...
題意:
亞瑟王要在圓桌上召開騎士會議,為了不引發騎士之間的衝突,並且能夠讓會議的議題有令人滿意的結果,每次開會前都必須對出席會議的騎士有如下要求:
1、 相互憎恨的兩個騎士不能坐在直接相鄰的2個位置;
2、 出席會議的騎士數必須是奇數,這是為了讓投票表決議題時都能有結果。
如果出現有某些騎士無法出席所有會議(例如這個騎士憎恨所有的其他騎士),則亞瑟王為了世界和平會強制把他剔除出騎士團。
現在給定準備去開會的騎士數n,再給出m對憎恨對(表示某2個騎士之間使互相憎恨的),問有幾個騎士永遠無法開會。
注意:1、所給出的憎恨關係一定是雙向的,不存在單向憎恨關係。
2、由於是圓桌會議,則每個出席的騎士身邊必定剛好有2個騎士。即每個騎士的座位兩邊都必定各有一個騎士。
3、一個騎士無法開會,就是說至少有3個騎士才可能開會。
解題:
1.讀清楚題目要求,是“有幾個騎士不能和任何人形成任何的圓圈”!!這裡先稱他們為“悲劇騎士”,其他能開會的人稱“歡樂騎士”
2.能相鄰坐即意味著兩者沒有仇恨,那很容易聯想到建仇恨關係的補圖~G
3.在~G裡,悲劇騎士要麼不能與別人形成環,要麼只能和別人形成偶數環。換個角度,歡樂騎士是不僅能和別人形成環,且這些環裡至少有一個(n >= 3 && n % 2 != 0),就是說歡樂英雄至少存在在一個奇圈裡。
4.雙連通分量和奇圈有如下關係:
(1) 如果一個雙連通分量內的某些頂點在一個奇圈中(即雙連通分量含有奇圈),那麼這個雙連通分量的其他頂點也在某個奇圈中;
(2) 如果一個雙連通分量含有奇圈,則他必定不是一個二分圖。反過來也成立,這是一個充要條件。
第一個定理在這裡特別重要,藉助該定理我們可以把“騎士i是否至少存在於一個奇圈裡”轉換成“騎士i所在的雙連通分量是否存在奇圈”
5.Tarjan可以求出雙連通分量,每符合dfn[u] <= low[v]就出來一個雙連通分量,所以我們要在每出來一個雙連通分量的時候都判斷該雙連通分量是否存在奇圈,若存在,則該雙連通分量裡的所有騎士都有資格開會。
6.判斷奇圈用染色法,網上很多人直接用二分圖染色法,我覺得那是錯的,但是該題的資料弱,可以過=_= 得好好理解定理1才能比較好地理解奇圈染色法.
7.該題用點雙或邊雙的思維來做都可以,我是用點雙,網上較多的是邊雙,差別不大,點雙要處理一點小細節.
例如從棧裡取出當前雙連通塊的點時不能把割點也取出棧,因為割點可以屬於多個雙連通塊。
剛TLE的時候黃叔說“邊雙的題你用點來做當然不行啦”,嘻嘻~~
8.到後面感覺,染色是關鍵。。。很多細節,如要理解定理1才能寫好,理解不好就會寫出個二分圖染色;如要初始化color, yes,
小優寫的題解很棒,除了幾點要注意的,在評論裡有點出來。
http://blog.csdn.net/lyy289065406/article/details/6756821
這個人的做法和我的一樣:http://blog.csdn.net/tsaid/article/details/6895808
*/
#include <algorithm>#include <stdio.h>#include <stack>#include <string.h>using namespace std;#define MAXN 1005#define MAXM 1000005struct Edge { int v, next; } edge[MAXM * 2];int low[MAXN], dfn[MAXN], head[MAXN], result[MAXM], color[MAXN];bool path[MAXN][MAXN], label[MAXN],visit[MAXM * 2], yes[MAXN], in_stack[MAXN];int n, m, edge_num, Index;stack<int> S;//result[]記錄當前雙連通塊的騎士 color[]染色用//label[i]記錄i是否有資格開會 yes[i]表示當前雙連通塊是否包含騎士i visit[k]由於是無向圖,用來判斷邊是否重用 void init(){ edge_num = 0; Index = 1; memset(head, -1, sizeof(head)); memset(visit, false, sizeof(visit)); memset(path, false, sizeof(path)); memset(dfn, 0, sizeof(dfn)); memset(low, 0, sizeof(low)); memset(label, false, sizeof(label)); memset(color, 0, sizeof(color)); memset(yes, false, sizeof(yes)); memset(in_stack, false, sizeof(in_stack)); while(!S.empty()) S.pop();}void add_edge(int u, int v){ edge[edge_num].v = v; edge[edge_num].next = head[u]; head[u] = edge_num++;}bool dye(int u, int c, int f) //這裡忘記判斷父節點了{ bool flag = true; if(color[u]) return color[u] != c; color[u] = c; for(int k = head[u]; k != -1; k = edge[k].next) { int v = edge[k].v; if(!yes[v] || v == f) continue; if(dye(v, 3-c, u)) return true; //一開始是直接返回true,沒有後面的else,函數結尾直接return false,但是當是葉子節點的時候會出錯 else flag = false; } //color[u] = 0; //如果都不成功就抹掉標記,因為是dfs /// ??? //好像不用耶。。得好好理解定理1 return flag;}void judge(int u, int v){ int sum = 0, x; memset(yes, false, sizeof(yes)); do { x = S.top(); S.pop(); result[sum++] = x; yes[x] = true; in_stack[x] = false; } while(x != v); //最後的u不要出棧,因為割點可能屬於多個雙連通塊 result[sum++] = u; yes[u] = true; //這是最後改的點。。。。終於AC了。。。 //printf(" $$$ "); //for(int i = 0 ; i < sum; i++) printf("%d ", result[i]); //printf("\n"); for(int i = 0; i < sum; i++) color[result[i]] = 0; //抹掉標記 //之前放在後面,當然會WA啦!! if(sum < 3 || !dye(result[0], 1, -1)) return ; //printf(" $$$ "); //for(int i = 0 ; i < sum; i++) printf("%d(%d) ", result[i], color[result[i]]); //printf("\n"); for(int i = 0; i < sum; i++) label[result[i]] = true; //標記這些人有開會資格}void Tarjan(int u){ dfn[u] = low[u] = Index++; S.push(u); in_stack[u] = true; for(int k = head[u]; k != -1; k = edge[k].next) if(!visit[k]) { visit[k] = visit[k ^ 1] = true; int v = edge[k].v; if(!dfn[v]) { Tarjan(v); low[u] = min(low[u], low[v]); if(low[v] >= dfn[u]) { judge(u, v); //遇到割點,判斷當前雙連通分量有沒有奇圈。 } } else if(in_stack[v]) low[u] = min(low[u], dfn[v]); //為什麼要判斷是否in_stack? }}int work(){ int x, y; init(); while(m--) { scanf("%d%d", &x, &y); if(path[x][y]) continue; path[x][y] = path[y][x] = true; } for(int i = 1; i <= n; i++) for(int j = i+1; j <= n; j++) if(path[i][j] == false) { //這裡沒處理好,TLE了好多次。一定要(i,j),(j,i)連著加到存邊數組裡面,不然Tarjan裡面 //visit[k] = visit[k^1] = true 就錯了,之前沒注意到 add_edge(i, j); add_edge(j, i); } for(int i = 1; i <= n; i++) if(!dfn[i]) Tarjan(i); int sum = 0; for(int i = 1; i <= n; i++) sum += (label[i] == false); //for(int i = 1; i <= n; i++) if(label[i] == false) printf("# %d\n", i); //printf("\n\n"); //printf("=================\n"); //for(int i = 1; i <= n; i++) { // for(int k = head[i]; k != -1; k = edge[k].next) { // printf("-- %d %d\n", i, edge[k].v); // } // printf("\n"); //} return sum;}int main(){ while( scanf("%d%d", &n, &m) , n+m ) printf("%d\n", work()); return 0;}