點連通分量的例題
貼一下點連通分量的模板
int dfs_c,bcc_cnt;
int bccno[SIZE_D],iscut[SIZE_D],pre[SIZE_D];
struct Edge{int v,u;};
stack<Edge> sta;
vector<int> bcc[SIZE_D];
int untarjan(int ver,int fa)//點連通分量
{
int lowver = pre[ver] = ++dfs_c;//父節點的時間戳賦值爲dfs——c,pre代表當前節點的時間戳。不改變
int child = 0;
for (int i = head[ver]; i != -1; i = pra[i].next){
int u = pra[i].to;
Edge temp = (Edge){ver,u};//把父節點和子節點存成邊放進去存起來
if (!pre[u]){//如果子節點沒有被訪問過
sta.push(temp);//把邊放進去棧
child++;//子節點個數加一
int lowu = untarjan(u,ver);//dfs。這樣就能存進去新的點
lowver = min(lowu,lowver);//父節點的時間戳存成最早的父子節點那個時間戳
if (lowu >= pre[ver]){//如果子節點的時間戳在???
iscut[ver] = 1;
bcc_cnt++;//表示連通分量的個數
printf("ver%d, u%d, fa%d, bcc_cnt%d, lowu%d, lowver%d\n",ver, u, fa, bcc_cnt, lowu, lowver);
bcc[bcc_cnt].clear();//清空vector
while (1){
Edge x = sta.top(); sta.pop();
if (bccno[x.v] != bcc_cnt){//如果這個節點所屬的連通分量 不是當前聯通分量,放進去
bcc[bcc_cnt].push_back(x.v);
bccno[x.v] = bcc_cnt;
}
if (bccno[x.u] != bcc_cnt){
bcc[bcc_cnt].push_back(x.u);
bccno[x.u] = bcc_cnt;
}
printf("%d %d\n",x.u,x.v);
if (x.u == u && x.v == ver)//如果棧頂元素就是當前邊
break;
}
}
}else if (pre[u]< pre[ver] && u != fa){//如果子節點的??? 小於父親節點。而且子節點不是當前節點的父親
sta.push(temp);
lowver = min (lowver, pre[u]);
}
}
if (fa < 0 && child == 1)
iscut[ver] = 0;//根據名字 我覺得這個數組的意思是 他是不是一個割點
return lowver;
}
解釋一下這個模板。
把父節點和子節點都存一個時間戳。然後不斷dfs,同時更新lowver,即父節點 最早的時間戳
如果更新成功,那就把棧裏的邊都取出來,放進同一個連通分量裏面
如果子節點已經被訪問過了,說明形成了一個環,更新lowver,即父節點 最早的時間戳,並且返回該時間戳的值
同時裏面加上一個判斷子節點個數的child變量。如果一個節點的父親是-1而且只有一個孩子,那麼這個根節點並不是割點
poj2942
題意就是:有些騎士互相憎恨。這些騎士不能挨着坐,現在有很多場圓桌會議,一場都不能參加的騎士會被趕走。問趕走多少個騎士
題解:給不互相憎恨的騎士之間連一條邊,然後,每個騎士左右坐的人可以就是連線的兩個人,所以不能存在葉子節點(也就是隻有一條邊和該節點相連).找出所有的圈。那麼就需要存在點雙連通分量纔可以有圈。
而且根據題意。必須是奇圈(包含奇數個節點的圈)
注意:含有奇圈的雙連通分量不一定是奇圈。因爲雙連通分量吧。可以有好多個圈,這些圈可以共用頂點。所以題意不明,造成了discuss裏面的爭論。根據ac的做法,每個騎士是可以同時參加一個以上的圓桌會議的
判斷奇圈只能用二分圖染色的方法。如果是一個奇圈,那麼一定不存在二分圖。這是充分必要條件,至於怎麼證的,我不懂
然後含有奇圈的雙連通分量裏的所有點都可以標記了。只用把沒標記的點驅逐就可以了
《算法競賽入門經典訓練指南?》上面的解法是找到每個字節點所屬的雙連通分量,判斷是不是當前的雙連通分量,然後染色,可是要是這個點剛好屬於那個不被標記的雙連通分量怎麼辦啊。不明白誒!我是直接找到雙連通分量裏的點,然後把該節點的子節點染色的。
/*
在不相互憎恨的騎士之間連一條邊,這樣如果有一個圈,那麼這個騎士的兩邊都有人坐了
如果是一個奇圈,那麼一定不存在二分圖。充分不必要條件
根據網上流傳的做法,題目是允許一個騎士坐在兩個圓桌上的
*/
#include<iostream>
#include<stack>
#include<vector>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define SIZE_D 2005
#define SIZE_B 5000005
using namespace std;
int ishate[SIZE_D][SIZE_D];
int e,head[SIZE_D];
void init()
{
e = 0;
memset(head,-1,sizeof(head));
memset(ishate,0,sizeof(ishate));
}
struct pp
{
int to,next;
}pra[SIZE_B];
void addedge2(int x, int y)
{
pra[e].to = y;
pra[e].next = head[x];
head[x] = e++;
pra[e].to = x;
pra[e].next = head[y];
head[y] = e++;
}
int dfs_c,bcc_cnt;
int bccno[SIZE_D],iscut[SIZE_D],pre[SIZE_D];
struct Edge{int v,u;};
stack<Edge> sta;
vector<int> bcc[SIZE_D];
int untarjan(int ver,int fa)//點連通分量
{
int lowver = pre[ver] = ++dfs_c;//父節點的時間戳賦值爲dfs——c,pre代表當前節點的時間戳。不改變
int child = 0;
for (int i = head[ver]; i != -1; i = pra[i].next){
int u = pra[i].to;
Edge temp = (Edge){ver,u};//把父節點和子節點存成邊放進去存起來
if (!pre[u]){//如果子節點沒有被訪問過
sta.push(temp);//把邊放進去棧
child++;//子節點個數加一
int lowu = untarjan(u,ver);//dfs。這樣就能存進去新的點
lowver = min(lowu,lowver);//父節點的時間戳存成最早的父子節點那個時間戳
if (lowu >= pre[ver]){//如果子節點的時間戳在???
iscut[ver] = 1;
bcc_cnt++;//表示連通分量的個數
printf("ver%d, u%d, fa%d, bcc_cnt%d, lowu%d, lowver%d\n",ver, u, fa, bcc_cnt, lowu, lowver);
bcc[bcc_cnt].clear();//清空vector
while (1){
Edge x = sta.top(); sta.pop();
if (bccno[x.v] != bcc_cnt){//如果這個節點所屬的連通分量 不是當前聯通分量,放進去
bcc[bcc_cnt].push_back(x.v);
bccno[x.v] = bcc_cnt;
}
if (bccno[x.u] != bcc_cnt){
bcc[bcc_cnt].push_back(x.u);
bccno[x.u] = bcc_cnt;
}
printf("%d %d\n",x.u,x.v);
if (x.u == u && x.v == ver)//如果棧頂元素就是當前邊
break;
}
}
}else if (pre[u]< pre[ver] && u != fa){//如果子節點的??? 小於父親節點。而且子節點不是當前節點的父親
sta.push(temp);
lowver = min (lowver, pre[u]);
}
}
if (fa < 0 && child == 1)
iscut[ver] = 0;//根據名字 我覺得這個數組的意思是 他是不是一個割點
return lowver;
}
/*
把父節點和子節點都寸一個時間戳。然後不斷dfs,同時更新lowver,即父節點 最早的時間戳
如果更新成功,那就把棧裏的邊都取出來,放進同一個連通分量裏面
如果子節點已經被訪問過了,說明形成了一個環,更新lowver,即父節點 最早的時間戳,並且返回該時間戳的值
同時裏面加上一個判斷子節點個數的child變量。如果一個節點的父親是-1而且只有一個孩子,那麼這個根節點並不是割點
*/
void find_bcc(int n)
{
memset(pre,0,sizeof(pre));
memset(iscut, 0, sizeof(iscut));
memset(bccno, 0, sizeof(bccno));
dfs_c = bcc_cnt = 0;
for (int i = 1; i <= n; i++)
if (!pre[i])
untarjan(i,-1);
}
int color[SIZE_D];
int odd[SIZE_D];
int bi(int ver,int b,int x)
{
color[ver] = x;
for (int j = 0; j < bcc[b].size(); j++){
int u = bcc[b][j];
if (u != ver && ishate[ver][u] == 0){
if (color[u] < 0){
if (!bi(u,b,x^1) )
return 0;
}else{
if (color[u]!= x^1)
return 0;
}
}
}
return 1;
}
int bi2(int ver,int b,int x)//書上的算法並沒有考慮一個點屬於兩個不同的聯通分量的情況
{
color[ver] = x;
for (int i = head[ver]; i != -1; i= pra[i].next){
int u = pra[i].to;
if (bccno[u] != b)
continue;
if (color[u] < 0){
if (!bi(u,b,x^1) )
return 0;
}else{
if (color[u]!= x^1)
return 0;
}
}
return 1;
}
int main()
{
freopen("input.txt","r",stdin);
int N,M;
while (~scanf("%d %d",&N,&M)){
if (N == 0 && M == 0)
break;
init();
int tempx,tempy;
for (int i = 1; i <= M; i++){
scanf("%d %d",&tempx,&tempy);
ishate[tempx][tempy] = 1;
ishate[tempy][tempx] = 1;
}
for (int i = 1; i <= N; i++)
for (int j = i+1; j <= N; j++){
if (ishate[i][j] != 1){
addedge2(i,j);
}
}
find_bcc(N);
memset(odd,-1,sizeof(odd));
for (int i = 1; i <= bcc_cnt; i++){//bcc_cnt表示雙連通分量的個數
memset(color, -1,sizeof(color));
for (int j = 0; j < bcc[i].size(); j++)
bccno[bcc[i][j]] = 1;
int u = bcc[i][0];
color[u] = 1;
if (!bi(u,i,1)){
//printf("i=%d first = %d\n",i,u);
for (int j = 0; j <bcc[i].size(); j++)
odd[bcc[i][j]] = 1;
}
}
int res = 0;
for (int i = 1; i <= N; i++){
if (odd[i] < 0)
res++;
}
printf("%d\n",res);
}
return 0;
}