題意:一些骑士,他们有些人之间有矛盾,现在要求选出一些骑士围成一圈,圈要满足如下条件:1.人数大于1。2.总人数为奇数。3.有仇恨的骑士不能挨着坐。问有几个骑士不能和任何人形成任何的圆圈。
分析:双连通分支。
建图方法是将没有矛盾的骑士连边,求双连通分量(关于点的)。对于每个双连通分量,看是否存在奇环,若存在那么这个双连通分量中的任意两骑士都可以同时出现在一个奇环里。原因如下:
因为每个不在那个奇环里的点都一定能找到两条连到奇环上不同节点的路径,然后奇环被分割为一个总数为奇数的节点串和一个总数为偶数的节点串,这时做出正确的选出和那一串构成环即可形成奇环。
那么如何判断一个双连通分量是否存在奇环呢?
用交叉染色法判断是不是二分图,一个图是二分图是不存在奇环的充要条件。注意,交叉染色法可以用dfs实现也可以用bfs实现。
还要注意由于有些顶点同时属于多个双连通分量,计算这些顶点是否是答案的时候不要算重复了。
重点说一下求点双连通分支的方法,我网上查到的所有版本对这个算法的描述都是错的。
首先,用tarjan算法,dfs遍历全图,用dfs_dep数组记录每个点在遍历过程中的深度,用low_point数组记录每个点的邻居中(不包括父亲)深度最浅的节点,把遍历过程中所有树枝边入栈。我们在遍历过程中,对于一个节点u,如果在遍历完成它的某子节点v之后,发现low_point[v]==dfs_dep[u]则说明u与v及其子孙构成一个点双连通分量,我们不停弹栈直到边(u,v)被弹出,和这些边相关的点构成一个点双连通分量。当我们遍历完点u的所有子孙之后,若发现low_point[u]==dfs_dep[u],则说明u不会再与其祖宗节点构成点双连通分量,但此时还有一条u的父亲和u的连边存在于栈顶,我们要把它弹出并丢弃。
#include <iostream> #include <cstdio> #include <cstring> #include <cstdlib> #include <algorithm> using namespace std; #define maxn 1005 #define maxm 1000005 struct Edge { int v, next; } edge[maxm * 2]; struct Elem { int u, v; Elem() { } Elem(int uu, int vv) : u(uu), v(vv) { } } stk[maxm * 2]; int head[maxn]; int cnt; int dfs_dep[maxn]; int low_point[maxn]; bool vis[maxn]; int top; bool is_cut_vertex[maxn]; int n, m; bool is_current_component[maxn]; bool counted[maxn]; int ans; void addedge(int a, int b) { edge[cnt].v = b; edge[cnt].next = head[a]; head[a] = cnt++; } void init() { memset(is_current_component, 0, sizeof(is_current_component)); } void add(Elem &a) { is_current_component[a.u] = true; is_current_component[a.v] = true; } int color[maxn]; int q[maxn]; int count_it() { int ret = 0; for (int i = 0; i < n; i++) if (is_current_component[i] && !counted[i]) { counted[i] = true; ret++; } return ret; } int judge(int a) { int front = 0, rear = 0; memset(color, -1, sizeof(color)); q[rear++] = a; color[a] = 0; while (front != rear) { int u = q[front++]; for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].v; if (!is_current_component[v]) continue; if (~color[v] && color[v] == color[u]) return count_it(); if (color[v] == -1) { q[rear++] = v; color[v] = (color[u] ^ 1); } } } return 0; } void tarjan(int dep, int parent, int u) { int descendant = 0; dfs_dep[u] = low_point[u] = dep; vis[u] = true; stk[top++] = Elem(parent, u); for (int i = head[u]; ~i; i = edge[i].next) { int v = edge[i].v; if (v == parent) continue; if (vis[v]) { low_point[u] = min(low_point[u], dfs_dep[v]); continue; } tarjan(dep + 1, u, v); low_point[u] = min(low_point[u], low_point[v]); if (low_point[v] >= dep) descendant++; if (low_point[v] == dep) { init(); while (!(stk[top].u == u && stk[top].v == v)) add(stk[--top]); ans += judge(v); } } if (low_point[u] == dep) top--; if (parent == -1) descendant--; if (descendant > 0) is_cut_vertex[u] = true; } bool g[maxn][maxn]; void input() { memset(g, 0, sizeof(g)); for (int i = 0; i < m; i++) { int a, b; scanf("%d%d", &a, &b); a--; b--; g[a][b] = g[b][a] = true; } for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) if (i != j && !g[i][j]) addedge(i, j); } int main() { //freopen("t.txt", "r", stdin); while (scanf("%d%d", &n, &m), n | m) { memset(head, -1, sizeof(head)); memset(vis, 0, sizeof(vis)); memset(counted, 0, sizeof(counted)); cnt = 0; input(); ans = 0; for (int i = 0; i < n; i++) if (!vis[i]) tarjan(0, -1, i); printf("%d\n", n - ans); } return 0; }