题意:有N个骑士,其中某些骑士互相憎恨对方,问要能使奇数个骑士开圆桌会议(憎恨的对方不能在一起)最少需要开除多少人?
题解:注意题意不是要求只开一次使人数达到最大的会议,而是删除那些永远不可能参加圆桌会议的人。
- 易知根据输入建立骑士能坐在一起的无向图(不一定连通)。
- 求出所有双连通分量,可参考本博对Tarjan论文的翻译。
- 在双连通分量里找是否有奇数个点的环(参考CSDN小优),若能找到奇圈,说明双连通分量的点都能参加圆桌会议(可能不是一次会议),对所有人做标记。
- 统计不能参加会议的人数输出。
*输入较大,使用读优化&G++时间可极大优化。
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define maxN 1005
#define maxE 500000
int N;
char graph[maxN][maxN]; //无向连通图
char NoExpelled[maxN]; //驱逐表(1代表能参加会议)
char bcc[maxN][maxN]; //双连通分量图
char IsInBcc[maxN]; //双连通分量点集
char color[maxN]; //染色表
int number[maxN]; //点的深搜发现时间
int low[maxN]; //在深搜palm tree(树边和后向边集合)中此点能到达的最早点标号
int index; //全局发现时间
int stackTop; //边栈大小
struct edge
{
short x,y;
};
edge edgeStack[maxE];
bool checkOddCircle(short u)
{
for(short v = 1;v <= N;v++)
{
if(bcc[u][v])
{
if(color[v] != -1)
{
if(color[u] == color[v]) //找到奇圈则返回真
return true;
}
else
{
color[v] = !color[u];
if(checkOddCircle(v)) //直到找到奇圈才返回,若没找到则要继续遍历双连通分量
return true;
}
}
}
return false;
}
int BiConnect(short v,short u) //u is the father of v in spanning T
{
number[v] = low[v] = index++;
for(short w = 1;w <= N;w++)
{
if(w != u&&w != v&&graph[v][w]) //可行边并且不能是其本身以及其父节点
{
edge newEdge;
newEdge.x = v;
newEdge.y = w;
if(!number[w]) //w还没标记,则w将是v的一个子节点
{
edgeStack[stackTop++] = newEdge; //将边压入栈
BiConnect(w,v); //继续深搜
low[v] = min(low[v],low[w]); //深搜返回后调整low值
if(low[w] >= number[v]) //有双连通分量或者桥边
{
memset(color,0XFF,sizeof(color));
memset(bcc,0,sizeof(bcc));
memset(IsInBcc,0,sizeof(IsInBcc));
while((--stackTop)&&(edgeStack[(stackTop)].x != newEdge.x||edgeStack[(stackTop)].y != newEdge.y)) //将直到(v,w)的边出栈
{
bcc[edgeStack[stackTop].x][edgeStack[stackTop].y] = 1; //将边保存到双连通分量bcc
IsInBcc[edgeStack[stackTop].x] = IsInBcc[edgeStack[stackTop].y] = 1; //记录双连通分量中的点
}
bcc[edgeStack[stackTop].x][edgeStack[stackTop].y] = 1;
IsInBcc[edgeStack[stackTop].x] = IsInBcc[edgeStack[stackTop].y] = 1;
color[v] = 0;
if(checkOddCircle(v))
{
for(int i = 1;i <= N;i++) //有奇圈则记录奇圈中的点
{
if(IsInBcc[i])
{
NoExpelled[i] = 1;
}
}
}
}
}
else if(number[w] < number[v]) //(v,w)是后向边,说明有环路
{
edgeStack[stackTop++] = newEdge;
low[v] = min(low[v],number[w]);
}
}
}
return 0;
}
int init()
{
memset(graph,1,sizeof(graph)); //初始化图中所有点均连接
memset(NoExpelled,0,sizeof(NoExpelled));
memset(number,0,sizeof(number));
return 0;
}
int calcNum()
{
int num = 0;
for(short i = 1;i <= N;i++)
{
if(!NoExpelled[i])
{
num++;
}
}
return num;
}
int main()
{
freopen("C:\\Users\\ifuding\\Desktop\\input.txt","r",stdin);
int M;
int x,y;
while(~scanf("%d%d",&N,&M)&&N)
{
init();
while(M--)
{
scanf("%d%d",&x,&y);
graph[x][y] = graph[y][x] = 0;
}
for(int i = 1;i <= N;i++)
{
if(!number[i])
{
index = 1;
stackTop = 0;
BiConnect(i,0);
}
}
printf("%d\n",calcNum());
}
return 0;
}