【點雙連通分量+奇環判定】UVA1364 Knights of the Round Table
阿新 • • 發佈:2020-10-20
UVA1364 Knights of the Round Table
題意:求無向圖\(G\)上不在任何一個簡單奇環上的點的個數。
分析:首先知道兩個定理:
-
若雙連通分量\(C\)含奇環,則\(C\)上任意一個點都位於奇環上(不一定是同一個奇環)。
-
若雙連通分量\(C\)不含奇環,則它一定是個二分圖,反之也成立。
證明\(1\):設\(C\)上的奇環為\(K\),任取兩個\(K\)上的節點\(u_1,u_2\),由雙連通分量性質及奇環條件可知,\(u_1,u_2\)之間必存在兩條點不重複的路徑,且一條有偶數個點,另一條有奇數個點。再任意取\(C\)上另外一個節點\(v\),則\(v\)
那麼解題思路如下:找出\(G\)上所有雙連通分量\(C_i\)→判定\(C_i\)是否為二分圖→若\(C_i\)不是二分圖,則將\(C_i\)上所有點標記為在奇環上→統計沒有標記的點的個數,即為答案。
注意題目中給出圖的邊是有憎恨關係的邊,我們要處理的圖\(G\)是它的補圖,即,把所有沒有憎恨關係的節點都連上,有憎恨關係的不連。
#include<iostream> #include<algorithm> #include<cstring> #include<vector> #include<map> #include<set> #include<queue> #include<cstdio> #include<stack> #define mem(a,n) memset(a,n,sizeof(a)) #define f(i,a,b) for(int i=a;i<=b;i++) #define af(i,a,b) for(int i=a,i>=b;i--) #define fe(u,i) for(int i=head[u];i;i=e[i].next) using namespace std; typedef long long LL; const int INF = 20010509; const int maxn = 1e3 + 100; const int maxm = 2e4 + 100; int dfs_clock; int dfn[maxn], low[maxn]; int head[maxn], cnt = 0; int n, m; int iscut[maxn], bccno[maxn], bcc_cnt; int inoddround[maxn]; int col[maxn];//二分圖判定用 int hate[maxn][maxn]; struct Edge { int from, to, next; }e[maxm]; stack<Edge> s; //保留在當前BCC中的邊 vector<int> bcc[maxn]; //記錄位於bcc[i]上的所有點 void add(int from, int to, Edge eset[], int head[]) { cnt++; eset[cnt].from = from; eset[cnt].to = to; eset[cnt].next = head[from]; head[from] = cnt; } bool isbinary(int u,int c) { col[u] = c; for (int i = head[u]; i; i = e[i].next) { int v = e[i].to; if (bccno[v] != bccno[u]) continue; //不屬於同一個雙連通分量,跳過 if (col[v] == !col[u]) continue; if (col[v] == col[u]) return false; if (col[v]==-1 && !isbinary(v, !c)) return false; } return true; } int dfs(int u, int fa) { low[u] = dfn[u] = ++dfs_clock; int child = 0; for (int i = head[u]; i; i = e[i].next) { int v = e[i].to; if (!dfn[v]) { s.push(e[i]); child++; low[v] = dfs(v, u); low[u] = min(low[u], low[v]); if (low[v] >= dfn[u]) { iscut[u] = true; bcc_cnt++; bcc[bcc_cnt].clear(); for (;;) { Edge x = s.top(); s.pop(); if (bccno[x.from] != bcc_cnt) { bcc[bcc_cnt].push_back(x.from); bccno[x.from] = bcc_cnt; } if (bccno[x.to] != bcc_cnt) { bcc[bcc_cnt].push_back(x.to); bccno[x.to] = bcc_cnt; } if (x.from == u && x.to == v) break; } } } else if (dfn[v] < dfn[u] && v != fa) { s.push(e[i]); low[u] = min(low[u], dfn[v]); } } if (fa < 0 && child == 1) iscut[u] = false; return low[u]; } void find_bcc() { dfs_clock = bcc_cnt = 0; for (int i = 1; i <= n; i++) { if (!dfn[i]) dfs(i, -1); } } void init() { mem(e, 0); mem(head, 0); cnt = 0; mem(col, -1); mem(inoddround, false); mem(hate, 0); mem(dfn, 0); mem(low, 0); mem(iscut, 0); mem(bccno, 0); } int main(){ while (cin >> n >> m) { if (!n && !m) break; init(); for (int i = 1; i <= m; i++) { int u, v; cin >> u >> v; hate[u][v] = 1; } for (int i = 1; i <= n; i++) { for (int j = i + 1; j <= n; j++) { if (!hate[i][j]) { add(i, j, e, head); add(j, i, e, head); } } } find_bcc(); for (int i = 1; i <= bcc_cnt; i++) { mem(col, -1); //注意不同雙連通分量之間可能會有重複的點 //因此每次在判定二分圖之前都要初始化col for (int j = 0; j < bcc[i].size(); j++) { bccno[bcc[i][j]] = i; //主要是為了給割點一個bcc編號,這樣雙連通分量才能一筆畫判斷二分圖 } if (!isbinary(bcc[i][0], 0)) { for (int j = 0; j < bcc[i].size(); j++) { inoddround[bcc[i][j]] = true; } } } int ans = 0; for (int i = 1; i <= n; i++) { if (!inoddround[i]) ans++; } cout << ans << endl; } return 0; }