Knights of the Round Table UVA - 1364(tarjan 无向图 点-双连通分量&&思维)建图+点双+dfs染色

Knights of the Round Table UVA - 1364

题意:

有n个圆桌骑士。给你m个互相憎恶的关系。
相互憎恶的骑士不可以坐在一起,且围起来的圈必须是奇数圈。

思路(本题细节比较多):

  1. 首先a和b相互憎恶,那么我们可以抽象成图,a和b之间没有边(那么现在把 不互相憎恶的骑士 连起来
  2. 之后就有了一个图。题目就转换成了求不在任何一个简单奇圈上的结点数。如果图G不连通,应该对每个连通分量分别求解。
  3. 结论:(证明过程在后面)
    可以证明在一个双连通分量里,假如B不是二分图,那么它含有奇圈,可以任意找一个结点u构造几个奇圈

简单上的所有结点必然属于同一个双连通分量,因此需要先找出所有双连通分量。二分图是没有奇圈。因此我们只需要关注那些不是二分图的双连通分量。
。。。。。。。
虽然这些双连通分量一定含有奇圈,但是不是其中的每个结点都在奇圈上呢?换句话说,如果结点v所属的某一个双连通分量B(因为v可能属于多个双连通分量)不是二分图,v是否一定属于一个奇圈呢?
。。。。。。
问题在于,尽管B不是二分图意味着它一定包含一个奇圈C,这个c可能并不包含v。我们得想办法让v参与进来。
。。。。。如图
根据连通性,从v一定可以到达C中的某一个结点u1;根据双连通性,C中还应存在另一个结点u2,使得从v出发有两条不相交路径(除起点外,无公共结点),分别到u1和u2.由于在C中,从u1到u2的两条路径的长度一奇一偶,总能构造出一条经过v的奇圈

  1. 还有一个注意的地方是,一个割点,可能属于多个双连通分量,所以染色时,要重新标号。

AC

#include <iostream>
#include <cstring>
#include <algorithm>
#include <stack>
#include <vector>
#define mst(x,a) memset(x,a,sizeof(x))
#define fzhead EDGE(int _u, int _v, int _next)
#define fzbody u(_u), v(_v), next(_next)
#define pb push_back
#define For(i,x,y) for(int i=(x); i<=(y); i++)
#define fori(i,x,y) for(int i=(x); i<(y); i++)
using namespace std;
const int maxm=2e6+10;
const int maxn=1e3+10;
struct EDGE{
    int u,v,next;
    EDGE(){}
    fzhead:fzbody{}
}e[maxm];
int dfn[maxn],low[maxn],cut[maxn],bccno[maxn],dfs_clock,bcc_cnt;
/// bccno[maxn]存一个点是哪一个 点双连通分量。
int head[maxn],tot;
vector<int> bcc[maxn];
stack<EDGE> s;
void add(int bg, int to){
    e[tot]=EDGE(bg,to,head[bg]);
    head[bg]=tot++;
}
int root;
void tarjan(int u, int f){
    dfn[u]=low[u]=++dfs_clock;
    int child=0;
    for(int i = head[u]; i != -1; i = e[i].next){
        int v = e[i].v;
        EDGE edge = {u,v,0};
        if(!dfn[v]){
            s.push(edge);
            child++;
            tarjan(v,u);
            low[u] = min(low[u],low[v]);
            if(low[v] >= dfn[u]){
                if(u != root||child > 1)cut[u]=1;///判断是否是割点
                bcc[++bcc_cnt].clear();
                for(;;){
                    EDGE x = s.top();s.pop();
                    if(bccno[x.u] != bcc_cnt){
                        bcc[bcc_cnt].pb(x.u); bccno[x.u] = bcc_cnt;
                    }
                    if(bccno[x.v] != bcc_cnt){
                        bcc[bcc_cnt].pb(x.v); bccno[x.v] = bcc_cnt;
                    }
                    if(x.u == u && x.v == v)break;
                }
            }
        }else if(v != f && dfn[v] < dfn[u]){///有反向边
            s.push(edge);
            low[u] = min(low[u],dfn[v]);
        }
    }
}
int color[maxn];
bool dfs1(int u, int b){
    for(int i=head[u]; i!=-1; i=e[i].next){
        int v=e[i].v;
        if(bccno[v]!=b)continue;///这里只要是一个点联通分量时,才访问。
        if(color[v]==color[u])return false;
        if(!color[v]){
            color[v]=3-color[u];
            if(!dfs1(v,b))return false;
        }
    }
    return true;
}
int n,m;
int link[maxn][maxn],odd[maxn];
void init(){
    tot=2;
    bcc_cnt=dfs_clock=0;
    mst(head,-1);
    mst(dfn,0);
    mst(low,0);
    mst(cut,0);
    mst(bccno,0);
    mst(odd,0);
    while(!s.empty())s.pop();
}
int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    while(cin>>n>>m,n||m){
        int u,v;
        init();
        For(i,1,n)For(j,1,n)if(i!=j)link[i][j]=1;
        For(i,1,m)cin>>u>>v,link[u][v]=link[v][u]=0;
        For(i,1,n)For(j,i+1,n){
            if(!link[i][j])continue;
            add(i,j);add(j,i);
        }
        For(u,1,n){
            if(!dfn[u])root=u,dfs_clock=0,tarjan(u,-1);
        }
        For(i,1,bcc_cnt){
            fori(j,0,bcc[i].size())bccno[bcc[i][j]]=i;///避免一个割点属于多个点双联通。
            mst(color,0);
            int u=bcc[i][0];
            color[u]=1;
            if(!dfs1(u,i))fori(j,0,bcc[i].size())odd[bcc[i][j]]=1;
        }
        int ans=n;
        For(i,1,n)if(odd[i])ans--;
        cout<<ans<<endl;
    }
    return 0;
}
/*
5 5
1 4
1 5
2 5
3 4
4 5
0 0
*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值