和平委员会

https://loj.ac/problem/10097

题目描述

  给出n个党派,每个党派中有2个代表,若两个代表彼此厌恶就都不能成为委员会,求能否成立委员会,若能输出一种可行的方案。

思路

  典型的2-SAT问题,我们用1表示在委员会中,0表示不在委员会中,那么给出的约束条件就是某两个代表的值必须为有一个0,隐藏的约束条件为同党派两个代表中有且只有一个代表的值为1。因此对于两个彼此厌恶的代表a、b,我们假设同党派另两个代表分别为u、v,那么我们可以将u、b连边,v、a连边,这条边的意思是必须,即这条边所连接的点的值必须相同,而建有向边的原因是就是实际意义,不过事实上我们建的是反图,相当于选a必须选v,选b必须选u。接下来我们只要进行缩点,那么在同一个强连通分量中的点必定是同一个值,而如果同个党派的代表在同一个强连通分量里,显然不可能成立。

  接下来考虑如何构造一组解,在原图上构造一组解,我们显然需要按照原图拓扑排序来,因为无入边的节点显然限制更小。具体的构造方法是对于同一党派的两个人a,b,如果a所在的强连通分量拓扑序在b所在强连通分量的前面,那么就取a的值为1。这样做显然是对的,可以从对称性上去分析,事实上缩点后的图也一定是对称的,那么对于两个党派的四个代表A,A',B,B',如果B'是A的后代节点(A到B’有边),那么A’是B的后代节点,显然我们可以推得A’和B'互相厌恶,由于存的是反图,所以我们可以将不选择的标记进行传递,因此如果确定了A选,那么A'的前代节点肯定都不可选,而图的对称性告诉我们A的后代节点和A’的前代节点没有区别,这样就必定能构造出一组解。

  而实际上在反图上跑tarjan并不影响强连通分量的判定,而反图缩点后的DAG,显然在dfs序上是按照原图缩点后拓扑序排好的,不过是倒过来,也就是说得到的强连通分量的节点编号符合反图上的拓扑序,原图上的逆拓扑序,那就可以直接判定同党派两个代表所在强连通分量的大小即可。

代码

#include <bits/stdc++.h>
using namespace std;
const int N=8800<<1,M=20005<<1;

int nxt[M],to[M],head[N],tot;
void add_edge(int x,int y)
{
    nxt[++tot]=head[x];
    head[x]=tot;
    to[tot]=y;
}

int read()
{
    int res=0,w=1;
    char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){res=(res<<3)+(res<<1)+ch-'0';ch=getchar();}
    return res*w;
}

int dfn[N],low[N],st[N],top,idx,co[N],col;
void tarjan(int u)
{
    dfn[u]=low[u]=++idx;
    st[++top]=u;
    for(int i=head[u];i;i=nxt[i])
    {
        int v=to[i];
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(!co[v])low[u]=min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        co[u]=++col;
        while(st[top]!=u)
        {
            co[st[top]]=col;
            --top;
        }
        --top;
    }
}
int main() 
{
    int n,m;
    n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        int a=read(),b=read(),u,v;
        u=(a&1)?a+1:a-1;
        v=(b&1)?b+1:b-1;
        add_edge(u,b);add_edge(v,a);
    }
    for(int i=1;i<=n*2;i++)
        if(!dfn[i])tarjan(i);
    for(int i=1;i<=n;i++)
        if(co[i*2-1]==co[i*2])
        {
            printf("NIE");
            return 0;
        }
    for(int i=1;i<=n;i++)
        printf("%d\n",co[i*2]>co[i*2-1]?i*2:i*2-1);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/fangbozhen/p/11734792.html