传送门
亚瑟王要在圆桌上召开骑士会议,为了不引发骑士之间的冲突,并且能够让会议的议题有令人满意的结果,每次开会前都必须对出席会议的骑士有如下要求:
1、 相互憎恨的两个骑士不能坐在直接相邻的2个位置;
2、 出席会议的骑士数必须是奇数,这是为了让投票表决议题时都能有结果。
如果出现有某些骑士无法出席所有会议(例如这个骑士憎恨所有的其他骑士),则亚瑟王为了世界和平会强制把他剔除出骑士团。
现在给定准备去开会的骑士数n,再给出m对憎恨对(表示某2个骑士之间使互相憎恨的),问亚瑟王至少要剔除多少个骑士才能顺利召开会议?
注意:1、所给出的憎恨关系一定是双向的,不存在单向憎恨关系。
2、由于是圆桌会议,则每个出席的骑士身边必定刚好有2个骑士。即每个骑士的座位两边都必定各有一个骑士。
3、一个骑士无法开会,就是说至少有3个骑士才可能开会。
思路: 很明显我们可以根据m对憎恨关系建图, 也就是两点之间如果不憎恨就有一条边, 然后我们的目的就是找到图中存在于奇环中的那些点, ans = n - 那些点. 那么对于我们找到了一个环如何判断它的个数是奇数个点了(或者这个偶数大环可以被分割若干个奇环). 那么就要用到这个定理了, 是如果是偶数个数(并且内部不能分割成小奇环)的环那么它就是一个二分图, 所以我们用染色法判定下该环是不是二分图即可.
因为二分图的定义: 不含有含有奇数条边的环的图
AC Code
const int maxn = 1e3 + 5;
int cas=1;
int dfn[maxn], low[maxn], cut[maxn];
int n, m;
int cnt, head[maxn];
int dfs_id, cut_num;
int a[maxn], can[maxn], g[maxn][maxn];
bool vis[maxn];
stack<int >st;
struct node
{
int from, to, next;
}e[maxn*maxn];
void add(int u, int v)
{
e[cnt] = node{u, v, head[u]};
head[u] = cnt++;
}
void init()
{
Fill(dfn, 0); Fill(low, 0); Fill(cut, 0);
cnt = 0; Fill(head, -1); Fill(can, 0);
Fill(a, 0); Fill(g, 0);
dfs_id = cut_num = 0;
}
int color[maxn], flag;
void dfs(int u, int fa, int col)
{
if(!flag) return ; //flag=false, 后面就都没有必要再搜下去了.
if(!color[u]) color[u] = col; //如果该点没有被染色,就染上.
else if(color[u] != col){ //如果遇到将要染色的点不等于将要被染的色,则结束dfs,不是二分图.
flag = false;
return ;
}
else return ;
for(int i = head[u] ; ~i ; i = e[i].next) {
int to = e[i].to;
if (to == fa || !vis[to]) continue; // 只走在环中的点.
dfs(to, u, 3 - col);
}
}
void tarjan(int u,int fa)
{
int son = 0;
dfn[u] = low[u] = ++dfs_id;
for(int i = head[u] ; ~i ; i = e[i].next) {
int v = e[i].to;
if(v == fa) continue;
if(!dfn[v]){
son++; st.push(i);
tarjan(v, u);
low[u] = min(low[u], low[v]);
if(low[v] >= dfn[u]){
cut[u] = 1; int k = 0;
Fill(vis, false);
while (1) {
int id = st.top(); st.pop();
if (!vis[e[id].from]) a[++k] = e[id].from;
if (!vis[e[id].to]) a[++k] = e[id].to;
vis[e[id].from] = vis[e[id].to] = true;
if (e[id].from == u && e[id].to == v)
break;
}
Fill(color, 0); flag = 1;
if (k >= 3) dfs(u, -1, 1); // 判定该环.
if (!flag) { // 不是二分图, 也就是我们满足条件的点.
for (int i = 1 ; i <= k ; i ++) {
can[a[i]] = 1;
}
}
}
}
else if(dfn[v] < dfn[u]) {
st.push(i);
low[u] = min(low[u],dfn[v]);
}
}
if (fa == -1 && son > 1) cut[u] = 1;
}
void solve()
{
while(~scanf("%d%d", &n, &m)){
if (n + m == 0) break;
init();
for (int i = 1 ; i <= m ; i ++) {
int u, v;
scanf("%d%d", &u, &v);
g[u][v] = g[v][u] = 1;
}
for (int i = 1 ; i <= n ; i ++) {
for (int j = i + 1 ; j <= n ; j ++) {
if (!g[i][j]) {
add(i, j); add(j, i);
}
}
}
for(int i = 1 ; i <= n ; i ++) {
if(!dfn[i]) tarjan(i, -1);
}
int ans = 0;
for (int i = 1 ; i <= n ; i ++) {
if (!can[i]) ans++;
}
printf("%d\n", ans);
}
}