2-sat问题
什么是2-sat问题
学习资料
如给定
很抽象吧
举个例子
现在有
小A 讨厌 喜欢
小B 喜欢 喜欢
小C 讨厌 讨厌
显然选择
我们不妨把两种要求设为 a,b,,变量前加 ¬ 表示「不」,即「假」。上述条件翻译成布尔方程即:
怎么求解 2-SAT 问题?
使用强连通分量
对于每个变量
原式
说白了就是其中一个若不满足则另一个必须满足要求
建出来就长这样
那么我们通过一个强连通分量就可以判断矛盾情况是否可以相互到达
有了前置芝士,我们就可以看一道例题了
满汉全席
显然,
对于
就可以转化为 满式牛肉--->满式羊肉与汉式羊肉--->汉式牛肉
( 类比上述
建好图后,开始
(样例一的建图)
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define maxn 100010
using namespace std;
int head[maxn],low[maxn],dfs[maxn],Gro[maxn];
int vis[maxn];
int f,tot,cnt,group,num1,num2;
stack<int> q;
struct node{
int v,nex;}re[maxn];
void add(int u,int v)
{
++tot;
re[tot].v=v; re[tot].nex=head[u];
head[u]=tot;
}
void init()
{
memset(head,0,sizeof head);
memset(Gro,0,sizeof Gro);
memset(dfs,0,sizeof dfs);
memset(vis,0,sizeof vis);
memset(low,0,sizeof low);
f=tot=cnt=group=0;
num1=num2=0;
}
void tarjian(int x)
{
low[x]=dfs[x]=++cnt;
q.push(x); vis[x]=1;
for(int i=head[x];i;i=re[i].nex)
{
int v=re[i].v;
if(!dfs[v])
{
tarjian(v);
low[x]=min(low[x],low[v]);
}
else if(vis[v])
low[x]=min(low[x],dfs[v]);
}
if(low[x]==dfs[x])
{
int y;
group++;
do{
y=q.top();
q.pop(); vis[y]=0;
Gro[y]=group;
}
while(y!=x);
}
}
int T,n,m,k;
char s1[10],s2[10];
signed main()
{
scanf("%lld",&T);
while(T--)
{
scanf("%lld%lld",&n,&m);
init();
for(int i=1;i<=m;i++)// m 1~n h n+1~2n
{
cin>>s1>>s2;
k=1,num1=0;
while(s1[k]>='0'&&s1[k]<='9') num1=num1*10+s1[k++]-'0';
k=1,num2=0;
while(s2[k]>='0'&&s2[k]<='9') num2=num2*10+s2[k++]-'0';
if(s1[0]=='m')
{
if(s2[0]=='m') add(num1+n,num2),add(num2+n,num1);//分别是将h1连m2 同时将h2连m1
if(s2[0]=='h') add(num1+n,num2+n),add(num2,num1);//将h1连h2 同时将m2连m1
}
if(s1[0]=='h')
{
if(s2[0]=='m') add(num1,num2),add(num2+n,num1+n);
if(s2[0]=='h') add(num1,num2+n),add(num2,num1+n);
}
}
for(int i=1;i<=2*n;i++)
{
if(!dfs[i]) tarjian(i);
}
for(int i=1;i<=n;i++)
{
if(Gro[i]==Gro[i+n])
{
f=1; break;
}
}
if(f==1) printf("BAD\n");
else printf("GOOD\n");
}
return 0;
}
/*
2
3 4
m3 h1
m1 m2
h1 h3
h3 m2
2 4
h1 m2
m2 m1
h1 h2
m1 h2
*/
现在我们有了判断是否有解的方法
那么如何解决方案情况呢
这就需要认识到
显然,在
则会继续下去只到最后一个
这时候
也就是说
由
而
那么在解决方案中,就说明选择
反之选择
由此可得
举个例子 和平委员会
点击查看代码
#include<bits/stdc++.h>
#define maxn 100010
#define int long long
using namespace std;
int head[maxn],low[maxn],dfs[maxn],Gro[maxn];
int ver[maxn],nex[maxn];
int vis[maxn];
int f,tot,cnt,group;
stack<int> q;
void add(int u,int v)
{
ver[++tot]=v; nex[tot]=head[u];head[u]=tot;
}
int one(int x)
{
if(x&1) return x+1;
else return x-1;
}
void tarjan(int x)
{
dfs[x]=low[x]=++cnt;
q.push(x); vis[x]=1;
for(int i=head[x];i;i=nex[i])
{
int v=ver[i];
if(!dfs[v])
{
cout<<x<<"----"<<v<<endl;
tarjan(v);
low[x]=min(low[x],low[v]);
}
else
{
if(vis[v])
{
cout<<"find "<<v<<"---"<<x<<endl;
low[x]=min(low[x],dfs[v]);
}
}
}
if(dfs[x]==low[x])
{
cout<<"degin!!! "<<group+1<<" "<<x<<"--";
int y;group++;
do
{
y=q.top();Gro[y]=group;
q.pop(); vis[y]=0;
cout<<y<<"--";
}while(x!=y);
}
}
int n,m,u,v;
signed main()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%lld%lld",&u,&v);
add(u,one(v));
add(v,one(u));
}
/*
建边的本质是有u就一定去选择v
*/
for(int i=1;i<=2*n;i++)
{
if(!dfs[i]) tarjan(i);
}
for(int i=1;i<=2*n;i++)
{
cout<<Gro[i]<<" ";
}
for(int i=1;i<=2*n;i+=2)
{
if(Gro[i]==Gro[i+1])
{
printf("NIE\n");
return 0;
}
}
// cout<<endl<<endl;;
for(int i=1;i<=2*n;i+=2)
{
if(Gro[i]<Gro[i+1])
{
/*
所以这句话:if(Gro[i]<Gro[i+1]) ,意思就是:如果i+1到i有单向的连边,那么我们肯定是选i的呀,因为如果选了i+1,就要选i,不就自己把自己坑死了吗
*/
printf("%lld\n",i);
}
else printf("%lld\n",i+1);
/*由于 Tarjan 强连通的 DFS 特性,给每个强连通分量的标号其实就是反向的拓扑序(标号为 k 的强连通分量会比 k+1 更早退栈,即先进入栈中)。
所以选标号小的强连通分量会影响到更少的点。 所以就有了输出答案的代码:
*/
/*显然Tarjan是以DFS序进行的,在DFS过程中遇到第一个强连通分量说明一定是从之前的点到达的,直到最后一个scc,这时候在本题中,如果出现后来的scc可以到达
自己同类型委员会,此时就是矛盾的,因为DFS序就一定可以从原先点到此点,scc又保证此点一定能到原先点,故矛盾;
那么答案输出也是同理,DFS越深,拓扑序越大,而scc编号越小。 如果我们选择scc编号大的作为答案,编号大说明此scc(记为大scc)之后还有其他的scc(记为小scc),
就可能出现 大scc中有同组委员,且小scc也有同组委员,那么大scc就可以通过DFS序到达小scc,相当于选了大scc就会依赖小scc。同组委员就都选择了,故矛盾;
*/
}
return 0;
}
/*
2 4
1 3
1 4
2 3
2 4
2 3
1 4
2 3
5 6
1 3
1 4
3 5
2 6
7 9
6 8
*/
建边技巧
很好判断的建边方式,很好判断的可行性
然后就T飞了
显然建边时
那么就有了前后缀建边
以空间换时间
众所周知 2-sat 可以 以
但是会有
引入
我们先建立若干个辅助点,让每一个辅助点都和最终点相连。然后,按照从前往后的顺序连接辅助点。这样,只需要连一条边,就得到了一个点的 后缀。
(如上图,点 22 一条边就连到了 22 之后的所有对应点)
反之,如果按照从后往前的顺序连接辅助点,就得到了前缀。必须要建两排辅助点。
最终建的图如下:
点击查看代码
#include<bits/stdc++.h>
#define maxn 4000010
using namespace std;
int ver[maxn<<5],nex[maxn<<5],head[maxn<<5],tot;
int cnt,dfs[maxn],low[maxn],vis[maxn],Gro[maxn],group;
int st[maxn];
int n,m,k,u,v,a[maxn],num,top;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f |= c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
void add(int u,int v)
{
ver[++tot]=v,nex[tot]=head[u];head[u]=tot;
}
void tarjan(int x)
{
dfs[x]=low[x]=++cnt;
st[++top]=x; vis[x]=1;
for(int i=head[x];i;i=nex[i])
{
int v=ver[i];
if(!dfs[v])
{
tarjan(v);
low[x]=min(low[x],low[v]);
}
else
if(vis[v])
{
low[x]=min(low[x],dfs[v]);
}
}
if(dfs[x]==low[x])
{
int y; group++;
do
{
y=st[top]; top--;
Gro[y]=group; vis[y]=0;
}while(x!=y);
}
}
int main()
{
n=read();
m=read();
k=read();
for(int i=1;i<=m;i++)
{
u=read();
v=read();
add(u+n,v);
add(v+n,u);
}
int nn=(n<<1),nnn=3*n;
for(int i=1;i<=k;i++)
{
num=read();
for(int j=1;j<=num;j++)
{
a[j]=read();
add(a[j]+nn,a[j]+n);
add(a[j]+nnn,a[j]+n);
}
for(int j=1;j<num;j++)
{
add(a[j]+nnn,a[j+1]+nnn);
add(a[j+1]+nn,a[j]+nn);
add(a[j],a[j+1]+nnn);
add(a[j+1],a[j]+nn);
}
}
for(int i=1;i<=2*n;i++)
if(!dfs[i]) tarjan(i);
for(int i=1;i<=n;i++)
{
if(Gro[i]==Gro[i+n])
{
puts("NIE");
return 0;
}
}
puts("TAK");
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探