题目大意
有N个骑士,给出某些骑士之间的仇恨关系,每次开会时会选一些骑士开,骑士们会围坐在一个圆桌旁。一次会议能够顺利举行,要满足两个条件:1.任意相互憎恨的两个骑士不能相邻。2.开会人数为大于2的奇数。若某个骑士任何会议都不能参加,那么就必须将他踢出,给出骑士之间的仇恨关系,问最少需要踢出多少个骑士?
总体思路
依题意,把骑士看作节点,每个骑士向其不仇视的骑士连一条无向边。如果一个骑士所代表的节点在一个奇环内,则该骑士便可以与环内节点所代表的骑士一起开会,所以我们要找到那个奇环。
我们要先找到环,再看看它是不是奇的。
利用的性质
- 图中所有的环包含在图的点双连通分量中(因为环中不存在割点)(图的点双连通分量为图的极大不存在割点的子图)。
- 如果一个双连通分量中存在奇环,则该双连通分量中的所有节点都属于一个奇环(如果存在一个奇环,则该双连通分量中的其它节点必由两条路径与奇环中的两个节点相连(否则路径上就有割点了)。两个节点之间因为在奇环中,所以连接两节点奇环内必有两条路径,一条是奇的,一条是偶的。因为经过那个其它节点与这两个节点的路径长度是固定的,所以命题成立)。
求点双连通分量:Tarjan ——的一种方法
在基本Tarjan算法的基础上构造一个维护节点的栈,其保证栈内节点cur和cur以上的所有节点都位于一个或多个双连通分量中。如果cur是割点,使得cur是某个遍历出的相邻节点v所属的双连通分量中DfsN值最小的节点,则一直将栈内v及以上的所有节点和cur纳入一个双连通分量中,将v及以上所有节点出栈。
注意事项
- 永远记住如果cur是割点不代表所有与cur相邻的节点都属于不同的双连通分量!所以是出栈到v,而不是出栈到cur。
- 即使cur是根节点且cnt<1(其实在此题中cnt根本不需要),也要进行出栈操作,因为此算法是一边遍历一边设双连通分量,如果根遍历到的第一个v节点便与根属于一个双连通分量,cnt不小于1导致了跳过。
- 记住判断割点的依据是cur->DfsN <= v->Low,而不是cur->DfsN <= cur->Low。
- 根节点可能不是割点,但是根节点必须在一个点双连通分量中,所以要注意Dfs完后的出栈操作。
判断奇环——二分图判定
利用染色的方法,将节点分为黑白两种颜色,一个节点要尝试将下一个节点染成与自己不同的颜色。如果下一个节点已有与自己相同的颜色,则此非二分图,存在奇环。如果最终得到了一个二分图,则不存在奇环。
AC待优化代码:
#include <cstdio> #include <cstring> #include <algorithm> #include <vector> using namespace std; #define LOOP(i,n) for(int i=1; i<=n; i++) const int MAX_NODE = 5000, MAX_EDGE = 1000010 * 2; struct Node; struct Edge; struct Node { int Id, DfsN, Low, Color; bool InBlock, InTable;//inTable:能加入圆桌会议 Edge *Head; }_nodes[MAX_NODE]; struct Edge { Node *From, *To; Edge *Next, *Rev; Edge(){} Edge(Node *from, Node *to, Edge *next):From(from),To(to),Next(next){} }*_edges[MAX_EDGE]; int _vCount, _eCount, DfsCnt; vector<vector<Node*>> Blocks; vector<Node*> Stack; void Init(int n) { memset(_nodes, 0, sizeof(_nodes)); _eCount = DfsCnt = 0; Blocks.clear(); Stack.clear(); _vCount = n; } Edge *NewEdge(Node *from, Node *to, Edge *next) { _eCount++; if (_edges[_eCount]) *_edges[_eCount] = Edge(from, to, next); else _edges[_eCount] = new Edge(from, to, next); return _edges[_eCount]; } Edge *AddEdge(Node *from, Node *to) { Edge *e = NewEdge(from, to, from->Head); from->Head = e; return e; } void Build(int uId, int vId, bool is2d) { Node *u = uId + _nodes, *v = vId + _nodes; u->Id = uId; v->Id = vId; Edge *e1 = AddEdge(u, v); if (is2d) { Edge *e2 = AddEdge(v, u); e1->Rev = e2; e2->Rev = e1; } } void DeStack(Node *cut, Node *v) { Node *blockMember; vector<Node*> newBlock; Blocks.push_back(newBlock); while(!Stack.empty()) {//易忘点 blockMember = Stack.back(); Blocks.back().push_back(blockMember); Stack.pop_back(); if (blockMember == v) break; }; if (cut != NULL) Blocks.back().push_back(cut); } void Dfs(Node *u,Node *root) { u->DfsN = u->Low = ++DfsCnt; Stack.push_back(u); for (Edge *e = u->Head; e; e = e->Next) { if (!e->To->DfsN) { Dfs(e->To,root); u->Low = min(u->Low, e->To->Low); if (u->DfsN <= e->To->Low) DeStack(u, e->To); } else u->Low = min(u->Low, e->To->DfsN); } } void SetBlock() { for (int i = 1; i <= _vCount; i++) { if (!_nodes[i].DfsN) { Stack.clear(); Node *root = i + _nodes; Dfs(root, root); if (!Stack.empty()) DeStack(root, NULL); } } } bool HaveOrdCircle(Node *u, int curColor) { u->Color = curColor; for (Edge *e = u->Head; e; e = e->Next) { if (e->To->InBlock) { if (e->To->Color == -1) { if (HaveOrdCircle(e->To, !curColor)) return true; } else if (e->To->Color == curColor) return true; } } return false; } void SetTableMember() { int blockCnt = Blocks.size(); for (int i = 0; i < blockCnt; i++) { int nodeCnt = Blocks[i].size(); if (nodeCnt <= 2) continue; for (int j = 0; j < nodeCnt; j++) { Blocks[i][j]->InBlock = true; Blocks[i][j]->Color = -1; } for (int j = 0; j < nodeCnt; j++) if (HaveOrdCircle(Blocks[i][0], 0)) for (int j = 0; j < nodeCnt; j++) Blocks[i][j]->InTable = true; for (int j = 0; j < nodeCnt; j++) Blocks[i][j]->InBlock = false; } } int main() { #ifdef _DEBUG freopen("c:\noi\source\input.txt", "r", stdin); freopen("c:\noi\source\output.txt", "w", stdout); #endif static bool hate[MAX_NODE][MAX_NODE]; int totNode, totHate, a, b; while (~scanf("%d%d", &totNode, &totHate) && (totNode || totHate)) { Init(totNode); memset(hate, false, sizeof(hate)); for (int i = 1; i <= totHate; i++) { scanf("%d%d", &a, &b); hate[a][b] = hate[b][a] = true; } for (int i = 1; i <= totNode; i++) for (int j = 1; j <= totNode; j++) if (i != j && !hate[i][j]) Build(i, j, false); SetBlock(); SetTableMember(); int cnt = 0; for (int i = 1; i <= _vCount; i++) if (!_nodes[i].InTable) cnt++; printf("%d ", cnt); } return 0; }