洛谷4782 2-Sat模板 + 2-Sat学习笔记

qwq这个东西我询问了几位神仙

发现后面构造方案的时候只能感性理解

首先 2 S a t 2 - Sat 是用来求解类似 每个物品有一个属性,真或假,要求满足 m m 个形如二者同时存在,都不存在,只能存在一个之类的条件。

那么这时候就需要 2 S a t 2-Sat

这个算法的实现过程大概是

我们对于每一个点建立两个点 i i i + n i+n 表示这个点是真或者是假。

然后对于题目中给出的关系进行建边,一条 x > y x->y 的边表示如果我们选了 x x 这个状态,那么就必须选择 y y 对应的状态。

qwq以这个题为栗子。

因为二者是至少存在一个的关系
假设前者的1必须和后者的0至少存在一个

那么相当于如果选择前者的0,后者必须选择0,如果选择后者的1,前者必须也选1。

我们就将前者0对应的节点连着后者的0的节点,后面同理。

之后呢,我们会得到一个有向图,进行 t a r j a n tarjan 缩点之后呢,我们就可以进行可行性的 c h e c k check

不难发现,如果同一个点的两个状态在同一个强连通分量里面的话,一定是不合法的,因为我们没有办法同时选择他们。

qwq貌似如果在上述的情况,就一定存在合法情况了

但是我们应该怎么求这样一个方案呢

其实这里也是比较的感性

因为一个点的出边表示他的限制数。所以我们对于一个点,应该尽量选限制少的,那么应该这么做呢?

考虑建反图,然后出边变成入边,实际上就是求反图拓扑序在前面的。
也就是反图拓扑序比较小的。

而反图的拓扑序等于原图的 s c c scc 编号
所以直接取小的那个就行

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define mk make_pair
#define ll long long
using namespace std;
inline int read()
{
  int x=0,f=1;char ch=getchar();
  while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
  while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}
const int maxn = 2e6+1e2;
const int maxm = 2*maxn;
int point[maxn],nxt[maxm],to[maxm];
int cnt,n,m;
int vis[maxn],dfn[maxn],low[maxn];
int roo[maxn];
int bel[maxn],scc;
int s[maxn];
int top;
//对于构造方案,我们之所以根据真假两个点所在的scc来决定,是因为scc的编号相当于反图的拓扑序,之所以要求反图的拓扑序选小的。
//这里我询问了几位爷 发现这个东西只能够感性理解。因为反图的拓扑序小,相当于限制少,那么选他构成合法方案的可能性就会更大,就能避开无解的情况
void addedge(int x,int y)
{
    ///cout<<x<<" "<<y<<endl;
    nxt[++cnt]=point[x];
    to[cnt]=y;
    point[x]=cnt;
} 
int tot;
void tarjan(int x)
{
//	cout<<x<<" "<<dfn[x]<<endl; 
    dfn[x]=low[x]=++tot;
    vis[x]=1;
    s[++top]=x;
//	cout<<x<<endl;
    for (int i=point[x];i;i=nxt[i])
    {
        //cout<<1<<endl;
        int p = to[i];	
        if (!dfn[p])
        {
            tarjan(p);
            low[x]=min(low[x],low[p]);
        }
        else
          if (vis[p])
            low[x]=min(low[x],dfn[p]); 
    }
//	cout<<1<<endl;
    if (low[x]==dfn[x])
    {
        scc++;
        while(s[top+1]!=x)
        {
        //cout<<top<<" "<<s[top+1]<<" "<<x<<endl;
            bel[s[top]]=scc;
            roo[s[top]]=x;
            vis[s[top]]=0;
            top--;
        }
    }
} 
int main()
{
  n=read();m=read();
  for (int i=1;i<=m;i++)
  {
  	 int x=read(),y=read(),z=read(),w=read();
  	 addedge(x+y*n,z+(w^1)*n);
  	 addedge(z+w*n,x+(y^1)*n);
  }
  for (int i=1;i<=2*n;i++)
  {
  	if (!dfn[i]) tarjan(i); 
  }
  for (int i=1;i<=n;i++)
  {
  	if (bel[i] == bel[i+n])  
    {
    	cout<<"IMPOSSIBLE\n"<<"\n";
    	return 0;
    }
  }
  cout<<"POSSIBLE"<<"\n";
  for (int i=1;i<=n;i++)
  {
  	 if(bel[i]<bel[i+n]) cout<<"1 ";
  	 else cout<<"0 ";
  }
  return 0;
}

u p d a t e : update:
整理一下建图的方法。
模型一:两者(A,B)不能同时取
  那么选择了A就只能选择B’,选择了B就只能选择A’
  连边A→B’,B→A’

模型二:两者(A,B)不能同时不取
  那么选择了A’就只能选择B,选择了B’就只能选择A
  连边A’→B,B’→A

模型三:两者(A,B)要么都取,要么都不取
  那么选择了A,就只能选择B,选择了B就只能选择A,选择了A’就只能选择B’,选择了B’就只能选择A’
  连边A→B,B→A,A’→B’,B’→A’

模型四:两者(A,A’)必取A
  那么,那么,该怎么说呢?先说连边吧。
  连边A’→A

搬了一位巨爷的博客。

orzzzzzzzzz

猜你喜欢

转载自blog.csdn.net/y752742355/article/details/87339180
今日推荐