三个2-sat问题,我总结一下自己的经验,我还是太菜,好久没做2-sat,又不太会建图了
总结:2-sat问题的核心就是建模
建模的思想很重要:
1.首先要怎么才能看出来是2-sat问题,对于每一个点,都有两种选择,则可以考虑是2-sat
比如让你在n组里面选n个,每组2个东西,这样就是2-sat的模型
2.确定是2-sat问题之后,就是要确定,拥有两种选择的对象,比如下面的例题里,有的是x[i]的取值有0或者1两种,有的是一串钥匙有两个,但你只能选择其中一个,则对于一个钥匙,它的取值有取或者不取,而不是对于这一串,取第一个还是第二个, 还有个问题是石头剪刀布的输赢,对面出石头,你不输的话要出石头或者布,这就是两种选择
这个拥有两种选择的对象有n个,然后你建图就是考虑i和i+n,最后tarjan后找i和i+n是否矛盾。
3.建图,建图是核心问题,但是一旦能够确定2-sat的对象,我感觉建图还是相对容易的,只要细心,不少考虑情况即可
要找到题目给的必定的条件,比如a和b不能同时存在,则a->~b,b->~a,如果a和b必须同时存在,则a->b,b->a,这种都要靠自己的感觉,没有什么确定的答案
总而言之,虽然我有好多题都不会做,有的是自己想出来了,有的没有想出来,但是感觉2-sat的方法还是有套路可循的,就是确定问题,寻找对象,寻找其中的关系
下面来看例题把:
简单的就不赘述了
hdu 3715
http://acm.hdu.edu.cn/showproblem.php?pid=3715
给你一个程序的伪代码,以及a,b,c,三个数组,还有个位置的x数组,x数组元素值为0或者1,问你如何设置x数组的值,使能通过的层数最大
首先确定是2-sat问题,其次要求层数最大,二分即可,对于每次二分的层数mid,把1-mid给的a,b,c数组用于建图
有两种选择的对象是x数组,假设x[i]=0为点i,x[i]=1为点i+mid
c=0时,x[a[i]]和x[b[i]]不能同时为0,即a[i]->b[i]+mid ,b[i]->a[i]+x;
c=1时,a[i]->b[i],b[i]->a[i],a[i]+x->b[i]+x,b[i]+x->a[i]+x;
c=2时,a[i]+x->b[i],b[i]+x->a[i];
如此建图,跑tarjan判可行性,即可
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")
using namespace std;
#define MAX 405
#define MAXN 1000005
#define maxnode 10
#define sigma_size 2
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define lrt rt<<1
#define rrt rt<<1|1
#define middle int m=(r+l)>>1
#define LL long long
#define ull unsigned long long
#define mem(x,v) memset(x,v,sizeof(x))
#define lowbit(x) (x&-x)
#define pii pair<int,int>
#define bits(a) __builtin_popcount(a)
#define mk make_pair
#define limit 10000
//const int prime = 999983;
const int INF = 0x3f3f3f3f;
const LL INFF = 0x3f3f;
const double pi = acos(-1.0);
const double inf = 1e18;
const double eps = 1e-9;
const LL mod = 1e9+7;
const ull mxx = 1333331;
/*****************************************************/
inline void RI(int &x) {
char c;
while((c=getchar())<'0' || c>'9');
x=c-'0';
while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0';
}
/*****************************************************/
struct Edge{
int u,v,next;
}edge[MAXN];
int dfn[MAX],low[MAX],belong[MAX],sstack[MAX];
int head[MAX],tot,Index,top,cnt;// tot是建图//cnt是强联通分量个数
int n,m;//n是点对个数 //Index是tarjan序
int instack[MAX]; //top是tarjan的栈元素个数
int a[10005],b[10005],c[10005];
void init(){
mem(head,-1);
mem(instack,0);
mem(dfn,0);
mem(belong,0);
tot=0;
top=0;
cnt=0;
Index=0;
}
void add_edge(int a,int b){
edge[tot]=(Edge){a,b,head[a]};
head[a]=tot++;
}
void tarjan(int u){//判断可行只需要一个tarjan即可
dfn[u]=low[u]=++Index;
sstack[++top]=u;
instack[u]=1;
for(int i=head[u]; i!=-1; i=edge[i].next){
int v=edge[i].v;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(instack[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
++cnt;
while(1){
int k=sstack[top--];
instack[k]=0;
belong[k]=cnt;
if(k==u) break;
}
}
}
bool check(int x){
init();
for(int i=0;i<x;i++){
//if(a[i]==b[i]) continue;
if(c[i]==0){
add_edge(a[i],b[i]+n);
add_edge(b[i],a[i]+n);
}
else if(c[i]==1){
add_edge(a[i],b[i]);
add_edge(b[i],a[i]);
add_edge(a[i]+n,b[i]+n);
add_edge(b[i]+n,a[i]+n);
}
else if(c[i]==2){
add_edge(a[i]+n,b[i]);
add_edge(b[i]+n,a[i]);
}
}
for(int i=0;i<2*n;i++){
if(!dfn[i]) tarjan(i);
}
for(int i=0;i<n;i++){
if(belong[i]==belong[i+n]) return false;
}
return true;
}
int main(){
//freopen("in.txt","r",stdin);
int t;
cin>>t;
while(t--){
cin>>n>>m;
for(int i=0;i<m;i++) scanf("%d%d%d",&a[i],&b[i],&c[i]);
int l=1,r=m;
while(l<=r){
int mid=(l+r)/2;
//cout<<l<<" "<<r<<" "<<mid<<endl;
if(check(mid)) l=mid+1;
else r=mid-1;
}
cout<<r<<endl;
}
return 0;
}
hdu 1816
http://acm.hdu.edu.cn/showproblem.php?pid=1816
给你n串钥匙,每串上面有2个钥匙,一共2n个钥匙,都互不相同,一串上一个如果用了,另一个就不能使用,然后给你m层,每层上面有两个锁,对应两种钥匙(可能相同)
问你最多能跑多少层?
2-sat+二分,这题两种选择对象有点难找,早上做的不太认真,一直GG
首先同一串钥匙上的两个a,b,每个钥匙有取或者不取两种选择,所以我们找到了问题的对线
a->~b,b->~a,但是这只是一部分限制条件,因为你每次二分的时候,mid层上的门,也是有限制的,这个限制比较难发现,因为有两个锁,你必须打开其中之一的锁,比如两个锁分别是c,d,那么你至少得打开一个,所以并不是选了c就不能选d,而应该是选了~c就不能选~d,所以建图是~c->d,~d->c
然后跑tarjan即可(题意好像有点小问题,他说的是有m个门,但是最后个门不能同向下面,我理解成了只有m-1个门GG)
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")
using namespace std;
#define MAX 4445
#define MAXN 1000005
#define maxnode 10
#define sigma_size 2
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define lrt rt<<1
#define rrt rt<<1|1
#define middle int m=(r+l)>>1
#define LL long long
#define ull unsigned long long
#define mem(x,v) memset(x,v,sizeof(x))
#define lowbit(x) (x&-x)
#define pii pair<int,int>
#define bits(a) __builtin_popcount(a)
#define mk make_pair
#define limit 10000
//const int prime = 999983;
const int INF = 0x3f3f3f3f;
const LL INFF = 0x3f3f;
const double pi = acos(-1.0);
const double inf = 1e18;
const double eps = 1e-9;
const LL mod = 1e9+7;
const ull mxx = 1333331;
/*****************************************************/
inline void RI(int &x) {
char c;
while((c=getchar())<'0' || c>'9');
x=c-'0';
while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0';
}
/*****************************************************/
struct Edge{
int u,v,next;
}edge[MAX*MAX*2];
int dfn[MAX],low[MAX],belong[MAX],sstack[MAX];
int head[MAX],tot,Index,top,cnt;// tot是建图//cnt是强联通分量个数
//Index是tarjan序
int instack[MAX]; //top是tarjan的栈元素个数
int aa[2500],bb[2500];
int c[2500],d[2500];
int pre[2500];
int n,m;
void init(){
mem(head,-1);
mem(instack,0);
mem(dfn,0);
mem(belong,0);
tot=0;
top=0;
cnt=0;
Index=0;
}
void add_edge(int a,int b){
edge[tot]=(Edge){a,b,head[a]};
head[a]=tot++;
}
void tarjan(int u){//判断可行只需要一个tarjan即可
dfn[u]=low[u]=++Index;
sstack[++top]=u;
instack[u]=1;
for(int i=head[u]; i!=-1; i=edge[i].next){
int v=edge[i].v;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(instack[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
++cnt;
while(1){
int k=sstack[top--];
instack[k]=0;
belong[k]=cnt;
if(k==u) break;
}
}
}
bool check(int x){
init();
for(int i=0;i<n;i++){
add_edge(c[i],d[i]+2*n);
add_edge(d[i],c[i]+2*n);
}
for(int i=1;i<=x;i++){
add_edge(bb[i]+2*n,aa[i]);
add_edge(aa[i]+2*n,bb[i]);
}
for(int i=0;i<4*n;i++){
if(!dfn[i]) tarjan(i);
}
for(int i=0;i<2*n;i++){
if(belong[i]==belong[i+2*n]) return false;
}
return true;
}
int main(){
//freopen("in.txt","r",stdin);
while(cin>>n>>m){
if(n==0&&m==0) break;
for(int i=0;i<n;i++) scanf("%d%d",&c[i],&d[i]);
for(int i=1;i<=m;i++){
scanf("%d%d",&aa[i],&bb[i]);
}
int l=1,r=m;
while(l<=r){
int mid=(l+r)/2;
if(check(mid)) l=mid+1;
else r=mid-1;
}
cout<<r<<endl;
}
return 0;
}
hdu 4115
http://acm.hdu.edu.cn/showproblem.php?pid=4115
两个人石头剪刀布,一共n轮,m个限制条件,已知第一个人n次出的是什么,问你在限制下,第二个人可能n轮都不输么,限制条件是某两次出的必须一样,或者必须不一样
做的时候还没总结到这么多,做完这题瞬间对2sat有了更多理解,首先这题如何看出来是2sat呢,对于n轮,你都不能输,所以你每轮都有两个出的方法,所以是两种选择,即为c,d,其次对象是每一轮,最后就是找每轮里的矛盾(两种都出了)。
建图嘛就简单了呗,根据条件,如果a,b轮必须相同,if c[a]!=c[b]...,if c[a]!=d[b],... if d[a]!=c[b]... if d[a]!=d[b] ...
如果必须不同 if c[a]==c[b]... if c[a]==d[b]... if d[a]==c[b]... if d[a]==d[b]...
考虑这样是否涵盖了所有的限制条件,我可以说是的,每一轮里的每个都和另一轮里的两个都讨论过了,所以是全面的考虑
通过这两题,我对2-sat建图的方法又有了更深刻的理解,原来只是理解了简单的建图,会套模版而已
改天再战
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <string>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <sstream>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#pragma comment(linker,"/STACK:102400000,102400000")
using namespace std;
#define MAX 20005
#define MAXN 1000005
#define maxnode 10
#define sigma_size 2
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define lrt rt<<1
#define rrt rt<<1|1
#define middle int m=(r+l)>>1
#define LL long long
#define ull unsigned long long
#define mem(x,v) memset(x,v,sizeof(x))
#define lowbit(x) (x&-x)
#define pii pair<int,int>
#define bits(a) __builtin_popcount(a)
#define mk make_pair
#define limit 10000
//const int prime = 999983;
const int INF = 0x3f3f3f3f;
const LL INFF = 0x3f3f;
const double pi = acos(-1.0);
const double inf = 1e18;
const double eps = 1e-9;
const LL mod = 1e9+7;
const ull mxx = 1333331;
/*****************************************************/
inline void RI(int &x) {
char c;
while((c=getchar())<'0' || c>'9');
x=c-'0';
while((c=getchar())>='0' && c<='9') x=(x<<3)+(x<<1)+c-'0';
}
/*****************************************************/
struct Edge{
int u,v,next;
}edge[MAX*8];
int dfn[MAX],low[MAX],belong[MAX],sstack[MAX];
int head[MAX],tot,Index,top,cnt;// tot是建图//cnt是强联通分量个数
//Index是tarjan序
int instack[MAX]; //top是tarjan的栈元素个数
int c[10005],d[10005];
int n,m;
void init(){
mem(head,-1);
mem(instack,0);
mem(dfn,0);
mem(belong,0);
tot=0;
top=0;
cnt=0;
Index=0;
}
void add_edge(int a,int b){
edge[tot]=(Edge){a,b,head[a]};
head[a]=tot++;
}
void tarjan(int u){//判断可行只需要一个tarjan即可
dfn[u]=low[u]=++Index;
sstack[++top]=u;
instack[u]=1;
for(int i=head[u]; i!=-1; i=edge[i].next){
int v=edge[i].v;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(instack[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
++cnt;
while(1){
int k=sstack[top--];
instack[k]=0;
belong[k]=cnt;
if(k==u) break;
}
}
}
int main(){
//freopen("in.txt","r",stdin);
int t,kase=0;
cin>>t;
while(t--){
int n,m;
cin>>n>>m;
init();
for(int i=1;i<=n;i++){
int x;
scanf("%d",&x);
if(x==1){
c[i]=1;
d[i]=2;
}
else if(x==2){
c[i]=2;
d[i]=3;
}
else if(x==3){
c[i]=3;
d[i]=1;
}
}
for(int i=0;i<m;i++){
int x,y,z;
scanf("%d%d%d",&x,&y,&z);
if(z==0){
if(c[x]!=c[y]){
add_edge(x,y+n);
add_edge(y,x+n);
}
if(c[x]!=d[y]){
add_edge(x,y);
add_edge(y+n,x+n);
}
if(d[x]!=d[y]){
add_edge(x+n,y);
add_edge(y+n,x);
}
if(d[x]!=c[y]){
add_edge(x+n,y+n);
add_edge(y,x);
}
}
else{
if(c[x]==c[y]){
add_edge(x,y+n);
add_edge(y,x+n);
}
if(d[x]==d[y]){
add_edge(x+n,y);
add_edge(y+n,x);
}
if(c[x]==d[y]){
add_edge(x,y);
add_edge(y+n,x+n);
}
if(d[x]==c[y]){
add_edge(x+n,y+n);
add_edge(y,x);
}
}
}
for(int i=1;i<=2*n;i++){
if(!dfn[i]) tarjan(i);
}
int flag=0;
for(int i=1;i<=n;i++){
if(belong[i]==belong[i+n]) flag=1;
}
kase++;
printf("Case #%d: ",kase);
if(flag) printf("no\n");
else printf("yes\n");
}
return 0;
}