和平委员会
作者:互联网
题目描述
给出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; }
标签:原图,连通,分量,党派,和平,反图,节点,委员会 来源: https://www.cnblogs.com/fangbozhen/p/11734792.html