//http://blog.csdn.net/lyy289065406/article/details/6756821
//刘汝佳版
/*
大致题意:
亚瑟王要在圆桌上召开骑士会议,为了不引发骑士之间的冲突,并且能够让会议的议题有令人满意的结果,每次开会前都必须对出席会议的骑士有如下要求:
1、 相互憎恨的两个骑士不能坐在直接相邻的2个位置;
2、 出席会议的骑士数必须是奇数,这是为了让投票表决议题时都能有结果。
如果出现有某些骑士无法出席所有会议(例如这个骑士憎恨所有的其他骑士),则亚瑟王为了世界和平会强制把他剔除出骑士团。
现在给定准备去开会的骑士数n,再给出m对憎恨对(表示某2个骑士之间使互相憎恨的),问亚瑟王至少要剔除多少个骑士才能顺利召开会议?
注意:1、所给出的憎恨关系一定是双向的,不存在单向憎恨关系。
2、由于是圆桌会议,则每个出席的骑士身边必定刚好有2个骑士。即每个骑士的座位两边都必定各有一个骑士。
3、一个骑士无法开会,就是说至少有3个骑士才可能开会。
解题思路:
综合性非常强的图论题
1、补图
2、双连通分量
3、二分图
4、奇圈
5、判定一个图是否为二分图的方法:交叉染色法
6、Tarjan算法
1、 补图
图G的补图~G就是把图G原有的边全部删去,原本不存在的边全部连上。
2、双连通分量
简单来说,无向图G如果是双连通的,那么至少要删除图G的2个结点才能使得图G不连通。换而言之,就是图G任意2个结点之间都存在两条以上的路径连接(注意:路径不是指直接相连的边),那么双连通分量就是指无向图G的子图G’是双连通了。
3、二分图
二分图又叫二部图,这个百度百科有定义,了解一下二分图是什么样子的可以了,无需深入去了解。不懂得同学等到做二分图的题目时再认真学吧。
4、 奇圈
用一条线把奇数个点串连起来,所得到的闭合的圈就是奇圈了。其实奇圈就是有奇数个顶点的环。
5、交叉染色法判定二分图
初始化所有结点为无色(颜色0)状态,用DFS遍历一个图G的同时,顺便对结点染色(只染1、2色),注意遍历过的结点还可以再遍历重新上色。让遍历到某个时候在对结点t染色时,发现边s->t的另一个结点s已染色,且s的颜色与当前正在对t染的颜色相同,那么图G必定不是二分图。
这是因为想象一下二分图就像是河的两岸有两排结点,每染色一次则过河一次,那么相同颜色的结点必定在同一侧。一旦出现异侧有相同颜色的结点,就可以说明图G不是二分图了。
6、Tarjan算法
我希望大家主要去学习一下这个算法的基本原理,尤其是DFN数组和Low数组,还有什么是深搜树,什么是树枝边,什么是后向边。
学习一下Tarjan算法求割点的过程(注意我上文是建议大家不要用Tarjan算法去求解割点的题,但不是让大家不要看它求割点的过程),因为这个过程是求双连通分量的关键。
两条重要定理:
若某块不可染色为二分图,则该块存在奇圈;
若某块存在奇圈,那么该块中的所有点都存在与奇圈中;
*/
#include<stdio.h>
#include<string.h>
#include<vector>
#include<stack>
using namespace std;
const int maxn = 1005;
const int inf = 1<<30;
int n,m;
int time,bcc_cnt;
int low[maxn],dfn[maxn],iscut[maxn];
int bccno[maxn],ood[maxn]; //bccno记录每个点所属的联通分量
bool maps[maxn][maxn];
int color[maxn]; //判断二分图
vector<int>map[maxn],bcc[maxn]; //bcc存联通分量
struct edge
{
int u,v;
edge( int x,int y )
{
u = x; v = y;
}
};
stack<edge>S;
bool bipartite( int u,int b ) //起点
{
for( int i = 0; i < map[u].size(); i ++ )
{
int v = map[u][i];
if( bccno[v] != b )
continue;
if( color[v] == color[u] )
return false;
if( !color[v] )
{
color[v] = 3 - color[u];
if( !bipartite(v,b) )
return false;
}
}
return true;
}
void tarjan( int u, int fa )
{
int child = 0; //平行边
low[u] = dfn[u] = ++time;
for( int i = 0; i < map[u].size(); i ++ )
{
int v = map[u][i];
if( !dfn[v] )
{
child ++;
S.push( edge(u,v) ); //把遍历的每个边压入栈
tarjan( v,u );
low[u] = low[u] <= low[v] ? low[u]:low[v]; //用后代low更新自己
if( low[v] >= dfn[u] )
{
iscut[u] = true; //割点
bcc_cnt ++; bcc[bcc_cnt].clear();
while(1) //缩点
{
edge x = S.top(); S.pop();
if( bccno[x.u] != bcc_cnt )
{
bcc[bcc_cnt].push_back(x.u); bccno[x.u] = bcc_cnt;
}
if( bccno[x.v] != bcc_cnt )
{
bcc[bcc_cnt].push_back(x.v); bccno[x.v] = bcc_cnt;
}
if( x.u == u && x.v == v )
break;
}
}
}
else if( dfn[v] < dfn[u] && v != fa ) //用反向边更新自己
{
S.push( edge(u,v) ); //把遍历的每个边压入栈
low[u] = low[u] <= dfn[v] ? low[u]:dfn[v];
}
}
if( fa < 0 && child == 1 ) //根节点
iscut[u] = 0;
}
void find_bcc()
{
time = bcc_cnt = 0;
memset( low,0,sizeof(low) );
memset( dfn,0,sizeof(dfn) );
memset( bccno,0,sizeof(bccno) );
memset( iscut,0,sizeof(iscut) );
for( int i = 1; i <= n; i ++ )
{
if( !dfn[i] )
tarjan( i,-1 );
}
}
void init() //初始化
{
for( int i = 1; i <= n; i ++ )
{
map[i].clear();
bcc[i].clear();
}
while( !S.empty() ) S.pop();
memset( maps,0,sizeof(maps) );
}
int main()
{
//freopen( "data.in","r",stdin );
int u,v,d;
while( scanf("%d%d",&n,&m) == 2 , n )
{
init();
for( int i = 1; i <= m; i ++ )
{
scanf("%d%d",&u,&v);
maps[u][v] = maps[v][u] = true;
}
for( int i = 1; i <= n; i ++ ) //建补图
{
for( int j = i+1; j <= n; j ++ )
{
if( !maps[i][j] )
{
map[i].push_back(j);
map[j].push_back(i);
}
}
}
find_bcc(); //双联通分量
memset( ood,0,sizeof(ood) );
for( int i =1; i <= bcc_cnt; i ++ )
{
memset( color,0,sizeof(color) );
for( int j = 0; j < bcc[i].size(); j ++ ) //把每个联通分量中的点标记出来
bccno[bcc[i][j]] = i;
int u = bcc[i][0];
color[u] = 1;
if( !bipartite(u,i) ) //在第i个联通分量中判断是否为二分图
{
for( int j = 0; j < bcc[i].size(); j ++ )
ood[bcc[i][j]] = 1;
}
}
int ans = n;
for( int i = 1; i <= n; i ++ )
if( ood[i] )
ans --;
printf("%d\n",ans);
}
return 0;
}
poj 2942 圆桌武士 双连通分量(BCC)+二分图+奇圈判断
最新推荐文章于 2019-07-31 10:51:04 发布