[POJ2942]:Knights of the Round Table(塔尖+二分图染色法)


题目传送门


题目描述

亚瑟王要在圆桌上召开骑士会议,为了不引发骑士之间的冲突,并且能够让会议的议题有令人满意的结果,每次开会前都必须对出席会议的骑士有如下要求:

  1、相互憎恨的两个骑士不能坐在直接相邻的2个位置。

  2、出席会议的骑士数必须是奇数,这是为了让投票表决议题时都能有结果。 如果出现有某些骑士无法出席所有会议(例如这个骑士憎恨所有的其他骑士),则亚瑟王为了世界和平会强制把他剔除出骑士团。       

现在给定准备去开会的骑士数n,再给出m对憎恨对(表示某2个骑士之间使互相憎恨的),问亚瑟王至少要剔除多少个骑士才能顺利召开会议? 

注意:

  1、所给出的憎恨关系一定是双向的,不存在单向憎恨关系。

  2、由于是圆桌会议,则每个出席的骑士身边必定刚好有2个骑士。即每个骑士的座位两边都必定各有一个骑士。

  3、一个骑士无法开会,就是说至少有3个骑士才可能开会。


输入格式

输入包含多组测试。 每种情况都以包含两个整数1≤n≤10001≤m≤1000000的整数行开始。 数字n是骑士的数量。 接下来的m行描述哪个骑士憎恨哪个骑士。 m行中的每一行包含两个整数k1k2,这意味着骑士数k1和骑士数k2彼此讨厌(数字k1k21n之间)。
输入n=m=0时终止。


输出格式

对于每组测试,输出一个整数,表示必须被驱逐的骑士数量。


样例

样例输入:

5 5
1 4
1 5
2 5
3 4
4 5
0 0

样例输出:

2


题解

些许有些复杂,思维量较大。

注意亚瑟王会召开多次会议,可以参加其中任意一次会议的骑士就可以被保留。

考虑建出原图的补图,其定义为:原来相连的两个点现在不相连,原来不相连的两个点现在相连。

这样的话两个其实可以坐在一起的条件即为他们之间有连边,方便处理。

那么,如果一个骑士可以参加会议,当且仅当他在一个奇环里。

给出两个定理:

  1、如果一个双连通分量内的某些顶点在一个奇圈中(即双连通分量含有奇圈),那么这个双连通分量的其他顶点也在某个奇圈中。

  2、如果一个双连通分量含有奇圈,则他必定不是一个二分图。反过来也成立,这是一个充要条件。

显然利用塔尖求出每一个v-DCC,然后判断这个v-DCC是不是奇环即可。

这时就用到了交叉染色法,dfs时每一条边都和上一条边反色,当发现两条相邻的边同色时即为奇环。

特别注意:此题不能用万能头文件,否则会CE!!!

那些被CE打倒的大佬:

不过结局总会是好的:


代码时刻

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>//不要万能头!!!
using namespace std;
struct rec
{
    int nxt;
    int to;
}e[1000001];
int head[1001],cnt;
int dfn[1001],low[1001],sta[1001],tot,top;
bool par[1001],vis[1001];
int col[1001];
bool Map[1001][1001];
vector<int> dcc;
void pre_work()//多测不清空,爆零两行泪TAT……
{
    cnt=0;
    tot=0;
    top=0;
    memset(head,0,sizeof(head));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(sta,0,sizeof(sta));
    memset(vis,0,sizeof(vis));
    memset(Map,0,sizeof(Map));
}  
void add(int x,int y)//建边
{
    e[++cnt].nxt=head[x];
    e[cnt].to=y;
    head[x]=cnt;
}
bool dfs(int x,int color)//交叉染色法判奇环
{
    col[x]=color;
    for(int i=head[x];i;i=e[i].nxt)
    {
        if(!par[e[i].to])continue;
        if(col[e[i].to]==color)return 1;
        if(!col[e[i].to]&&dfs(e[i].to,-color))return 1;
    }
    return 0;
}
void color_solve()//将v-DCC转入数组,方便处理
{
    memset(par,0,sizeof(par));
    memset(col,0,sizeof(col));
    for(int i=0;i<dcc.size();i++)
        par[dcc[i]]=1;
    if(dfs(dcc[0],1))
        for(int i=0;i<dcc.size();i++)vis[dcc[i]]=1;
}
void tarjan(int x)//塔尖
{
    dfn[x]=low[x]=++tot;
    sta[++top]=x;
    for(int i=head[x];i;i=e[i].nxt)
    {
        if(!dfn[e[i].to])
        {
            tarjan(e[i].to);
            low[x]=min(low[x],low[e[i].to]);
            if(dfn[x]<=low[e[i].to])//发现v-DCC
            {
                int y;
                dcc.clear();
                do
                {
                    y=sta[top--];
                    dcc.push_back(y);
                }while(e[i].to!=y);
                dcc.push_back(x);
                color_solve();
            }
        }
        else low[x]=min(low[x],dfn[e[i].to]);
    }
}
int main()
{
    while(1)
    {
        int n,m;
        scanf("%d%d",&n,&m);
        if(!n&&!m)break;
        pre_work();
        for(int i=1;i<=m;i++)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            Map[x][y]=Map[y][x]=1;//临接矩阵存图
        }
        for(int i=1;i<=n;i++)
            for(int j=i+1;j<=n;j++)
                if(!Map[i][j]){add(i,j);add(j,i);}//链式前项星建反图
        for(int i=1;i<=n;i++)
            if(!dfn[i])tarjan(i);
        int ans=0;
        for(int i=1;i<=n;i++)//统计可以参加的骑士
            if(vis[i])ans++;
        printf("%d\n",n-ans);//用总骑士数减去
    }
    return 0;
}

 

rp++

posted @   HEOI-动动  阅读(158)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示