https://hihocoder.com/contest/hiho133/problem/1
题目1 : 2-SAT·hihoCoder音乐节
上午为真 ,下午为假
把点分成2*X,2*X+1
构图方法:
对于 a or b 的条件
转换成 ¬a->b AND ¬b->a
就是分别连两条边 ¬a->b , ¬b->a
最后对构出的图,跑强连通分量,判断 同一个点的真假如果在同一个强联通分量里,则矛盾。(即推出一首歌既要在上午播出又要在下午播出,即真假同时成立)
为什么只跑个强连通分量(tarjan)即可呢,因为原图中每一条边(a->b)的意义是 a可以推导出b
如果一个点拆成真假2x,2x+1两个点后在一个强联通里,则我们必须选一个值,不管选哪个,都会推导到真假同时成立,显然矛盾,反之,如果整个图所有拆出的点两两都不在一个强联通分量,则必然可以找出一组解.
#include<bits/stdc++.h>
using namespace std;
const int maxn=105*2;
int pre[maxn],lowlink[maxn],sccno[maxn],dfs_clock,scc_cnt;
vector<int> mp[maxn] ;
stack<int>st;
void add_edge(int x,bool xval,int y,bool yval)
{
x=x*2+xval;
y=y*2+yval;
mp[x^1].push_back(y);
mp[y^1].push_back(x);
}
void dfs(int u)
{
pre[u]=lowlink[u]=++dfs_clock;
st.push(u);
for (int i=0; i<mp[u].size(); i++)
{
int v=mp[u][i];
if (!pre[v])
{
dfs(v);
lowlink[u]=min(lowlink[u],lowlink[v]);
}
else if (!sccno[v])
lowlink[u]=min(lowlink[u],pre[v]);
}
if (lowlink[u]==pre[u])
{
scc_cnt++;
for (;;)
{
int x=st.top();
st.pop();
sccno[x]=scc_cnt;
if (x==u) break;
}
}
}
void find_scc(int n)
{
scc_cnt=dfs_clock=0;
memset(pre,0,sizeof pre);
memset(sccno,0,sizeof sccno);
for (int i=0; i<n; i++)
if (!pre[i]) dfs(i);
}
int main()
{
int t;
cin>>t;
char A[100],B[100];
while(t--)
{
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<=n*2;i++)
mp[i].clear();
getchar();
for(int i=1; i<=m; i++)
{
scanf("%s %s",A,B);
int numa=0,numb=0,lena=strlen(A),lenb=strlen(B);
for(int j=1;j<lena;j++) numa=numa*10+A[j]-'0';
for(int j=1;j<lenb;j++) numb=numb*10+B[j]-'0';
numa--,numb--;
if (A[0]=='m'&&B[0]=='m') add_edge(numa,1,numb,1);
if (A[0]=='m'&&B[0]=='h') add_edge(numa,1,numb,0);
if (A[0]=='h'&&B[0]=='h') add_edge(numa,0,numb,0);
if (A[0]=='h'&&B[0]=='m') add_edge(numa,0,numb,1);
}
find_scc(2*n);
int flag=1;
for(int i=0;i<2*n;i+=2)
if (sccno[i]==sccno[i+1]) flag=0;
if (flag)
printf("GOOD\n");
else
printf("BAD\n");
}
return 0;
}