【点双连通分量+奇环判定】UVA1364 Knights of the Round Table
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\)与\(u_1\),\(u_2\)之间有路径,其中点的个数为偶数的路径与\(u_1,u_2\)之间奇数个点的路径构成一个奇环,故\(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;
}