D35【模板】2-SAT

D35【模板】2-SAT_哔哩哔哩_bilibili

 

2-SAT问题详解-CSDN博客

2-SAT - OI Wiki

SAT 是适定性(Satisfiability)问题的简称.一般形式为 k-适定性问题,简称 k-SAT.而当 𝑘 >2 时该问题为 NP 完全的.所以只研究 𝑘=2 的情况.

2-SAT,给 𝑛 个布尔变量 𝑥1∼𝑥𝑛,𝑚 个需要满足的条件,每个条件的形式都是 𝑥𝑖 为 1/0 或 𝑥𝑗 为 1/0.给每个变量赋值使得所有条件得到满足. 输出一种可行方案即可.

设 𝑎 表示 𝑥𝑎 为真(¬𝑎 就表示 𝑥𝑎 为假).如果有个人提出的要求分别是 𝑎 和 𝑏,即 (𝑎∨𝑏)(变量 𝑎,𝑏 至少满足一个).对这些变量关系建有向图,则把 𝑎 成立或不成立用图中的点表示,¬𝑎 →𝑏 且 ¬𝑏 →𝑎,表示 𝑎 不成立 则 𝑏 一定成立;同理,𝑏 不成立 则 𝑎 一定成立

若两点在同一强连通分量内,则这两点代表的条件 要么都满足,要么都不满足

建图后使用 Tarjan 算法找 SCC,判断对于任意布尔变量 𝑎,表示 𝑎 成立的点和表示 𝑎 不成立的点是否在同一个 SCC 中,若有则输出无解,否则有解.

如果变量 𝑥 的拓扑序在 ¬𝑥 之后,那么取 𝑥 值为真.应用到 Tarjan 算法的缩点,即 𝑥 所在 SCC 编号在 ¬𝑥 之前时,取 𝑥 为真.因为 Tarjan 算法求强连通分量时使用了栈,如果跑完 Tarjan 缩点之后呈现出的拓扑序更大,在 Tarjan 会更晚被遍历到,就会更早地被弹出栈而缩点,分量编号会更小,所以 Tarjan 求得的 SCC 编号相当于 反拓扑序

算法会把整张图遍历一遍,由于这张图 𝑛 和 𝑚 同阶,计算答案时复杂度为 𝑂(𝑛),因此总复杂度为 𝑂(𝑛)

D14 强连通分量 Tarjan 算法 - 董晓 - 博客园 

P4782 【模板】2-SAT - 洛谷
// 2-SAT+tarjan O(n+m)
#include<bits/stdc++.h>
using namespace std;

const int N=2000005;
int n,m;
int hd[N],to[N],ne[N],idx;
int dfn[N],low[N],tim,stk[N],top,scc[N],cnt;

void add(int a,int b){
  to[++idx]=b,ne[idx]=hd[a],hd[a]=idx;
}
void tarjan(int x){
  dfn[x]=low[x]=++tim;
  stk[++top]=x;
  for(int i=hd[x];i;i=ne[i]){
    int y=to[i];
    if(!dfn[y]){
      tarjan(y);
      low[x]=min(low[x],low[y]);
    }
    else if(!scc[y]) low[x]=min(low[x],dfn[y]);
  }
  if(low[x]==dfn[x]){
    ++cnt;
    for(int y=-1;y!=x;) scc[y=stk[top--]]=cnt;
  }
}
int main(){
  scanf("%d%d",&n,&m);
  for(int i,a,j,b;m--;){
    scanf("%d%d%d%d",&i,&a,&j,&b);
    add(i+!a*n,j+b*n); //xi拆成i和i+n
    add(j+!b*n,i+a*n);
  }
  for(int i=1;i<=2*n;i++)if(!dfn[i]) tarjan(i);
  for(int i=1;i<=n;i++)if(scc[i]==scc[i+n]){
    puts("IMPOSSIBLE");
    return 0;
  }
  puts("POSSIBLE");
  for(int i=1;i<=n;i++)printf("%d ",scc[i]>scc[i+n]);
  return 0;
}

 

P4171 [JSOI2010] 满汉全席 - 洛谷
#include<bits/stdc++.h>
using namespace std;

const int N=205;
int t,n,m;
int head[N],idx;
struct Edge{int to,ne;}e[4005];
int dfn[N],low[N],tim,stk[N],top,scc[N],cnt;
char s1[5],s2[5];
 
void add(int a,int b){
  e[++idx].to=b;
  e[idx].ne=head[a];
  head[a]=idx;
}
void tarjan(int x){
  dfn[x]=low[x]=++tim;
  stk[++top]=x;
  for(int i=head[x];i;i=e[i].ne){
    int y=e[i].to;
    if(!dfn[y]){ //若y尚未访问
      tarjan(y);
      low[x]=min(low[x],low[y]);
    }
    else if(!scc[y]) //若y已访问且未处理
      low[x]=min(low[x],dfn[y]);
  }
  
  if(low[x]==dfn[x]){ //若x是SCC的根
    ++cnt;
    for(int y=-1;y!=x;) scc[y=stk[top--]]=cnt;
  }
}
int main(){
  scanf("%d",&t);
  while(t--){
    idx=tim=cnt=top=0;
    memset(head,0,sizeof head);
    memset(dfn,0,sizeof dfn);    
    memset(scc,0,sizeof scc);
    scanf("%d%d",&n,&m);
    while(m--){
      scanf("%s%s",&s1,&s2);
      int i=0,j=0,a,b,k;
      a=(s1[0]=='m'?0:1);
      b=(s2[0]=='m'?0:1);
      for(k=1;s1[k]>='0'&&s1[k]<='9';)
        i=i*10+s1[k++]-'0';
      for(k=1;s2[k]>='0'&&s2[k]<='9';)
        j=j*10+s2[k++]-'0';
      add(i+n*!a,j+n*b); 
      add(j+n*!b,i+n*a);
    }
    
    for(int i=1;i<=n<<1;++i)if(!dfn[i])tarjan(i);
    bool flag=0;
    for(int i=1;i<=n;++i)
      if(scc[i]==scc[i+n]){
        flag=1; break;
      }
    flag?puts("BAD"):puts("GOOD");
  }
}

 

posted @ 2024-08-02 10:18  董晓  阅读(658)  评论(0)    收藏  举报