2-sat问题总的来说就是把n个物品分成两个集合,或者说2*n个物品有n对,每对只能挑一个。实际上就是建模成,每一对选一个0表示第一个,1表示第二个(或一个物品选(1)与不选(0)),也就是用0,1来表示互相矛盾的物品,然后物品之间相互有一些限制关系:对于一对我们用 i0 和 i1 表示。两个物品分别为i,j。
1.与
1:两个都为1.也就是不能选0:那么建边
i0
到
i1
和
j0
到
j1
。(使其自相矛盾即可)
0:两个至少有一个为0:那么建边
i1
到
j0
和
j1
到
i0
。
2.或
1:至少有一个为1:那么建边
i0
到
j1
和
j0
到
i1
0:都为0,也就是不能选1:那么建边
i1
到
i0
和
j1
到
j0
3.异或
1:两者不同:建边
i0
到
j1
和
j1
到
i0
0:两者相同:建边
i0
到
j0
和
i1
到
j1
最后都可以归为这6种关系。然后建图跑强连通就可以了。
1.n个物品,选与不选的问题。如果i与j冲突,则建边:
head[i<<1].push_back(j<<1|1);
head[j<<1|1].push_back(i<<1);
head[j<<1].push_back(i<<1|1);
head[i<<1|1].push_back(j<<1);
2.2*n个物品,只能选一个的问题。如果有冲突,则建边:
if(check(i<<1,j<<1))head[i<<1].push_back(j<<1|1);
if(check(i<<1,j<<1|1))head[i<<1].push_back(j<<1);
if(check(i<<1|1,j<<1))head[i<<1|1].push_back(j<<1|1);
if(check(i<<1|1,j<<1|1))head[i<<1|1].push_back(j<<1);
输出解:拓扑序出来的一组就是解。
for(int i = 0; i < n; ++i)
{
ha[sccno[i<<1]].push_back(sccno[i<<1|1]);
ha[sccno[i<<1|1]].push_back(sccno[i<<1]);
}
for(int i = 0; i < n*2; ++i)
for(int j = 0,l = head[i].size(); j < l; ++j)
{
int u = sccno[i];
int v = sccno[head[i][j]];
if(u != v)
{
G[v].push_back(u);
du[u]++;
}
}
get_ans();
int color[MAXN];
int du[MAXN];
void get_ans()
{
queue<int>q;
for(int i = 1; i <= scc_cnt; ++i)if(!du[i])q.push(i);
while(!q.empty())
{
int u = q.front();
q.pop();
if(!color[u])
{
color[u] = 1;
for(int i = 0, l = ha[u].size(); i < l; ++i)
{
int v = ha[u][i];
color[v] = 2;
}
}
for(int i = 0, l = G[u].size(); i < l; ++i)
{
int v = G[u][i];
du[v]--;
if(!du[v])q.push(v);
}
}
puts("YES");
for(int i = 0; i < n; ++i)
{
if(color[sccno[i<<1]] == 1)
printf("%.2d:%.2d %.2d:%.2d\n",w[i<<1].s/60,w[i<<1].s%60,w[i<<1].e/60,w[i<<1].e%60);
else printf("%.2d:%.2d %.2d:%.2d\n",w[i<<1|1].s/60,w[i<<1|1].s%60,w[i<<1|1].e/60,w[i<<1|1].e%60);
}
}