2-SAT 问题是k-SAT问题在k==2时的特殊情况,因为已经证明k>=3时的k-sat问题属于npc问题。所以在这里仅研究2-SAT的特殊情况。
何为2-sat问题?
简单地说就是有N个集合,每个集合中有两个元素,其中一些集合中的元素存在相对关系。求出从每个集合中取出1个元素的方案。
先看一道例题:
2-sat的最简单情况,选出一些元素时,一些元素不可选。做这些题时,一般会考虑建图或者用并查集之类的数据结构维护。但显然复杂度是无法接受的,考虑新的算法。
首先,如果A1,A2,B1,B2分别为两个集合的两个元素。
且存在以下的排斥关系。
A1<->B2
B1<->A2
那么考虑从A1向B1连边,这条边意味着选了A1就必须选B1,因为显然选B2是不合法的。考虑相反情况。如果选了B2,就必须选A2,因为B2不可以和A1共存。 B1<->A2也是这样处理。
这时候我们就得到了一个图。把图中的一个强连通分量取出考虑,这个强连通分量里的每个元素都可以代表这一整个强连通分量,即选了这个强连通分量中的一个元素就必须选这个强连通分量里的所有元素,强连通分量里的元素部分取出构成的方案是不合法的(自己思考为什么)。同时,如果一个集合中的两个元素都位于一个强连通分量中也是不合法的。这时候各个元素中的相对关系就很显然了,采用topsort做最后的处理。
//COGS 313 //by Cydiater //2016.8.2 #include <iostream> #include <cstdio> #include <cstring> #include <string> #include <queue> #include <map> #include <algorithm> #include <cstdlib> #include <cmath> #include <ctime> #include <iomanip> using namespace std; #define ll long long #define up(i,j,n) for(int i=j;i<=n;i++) #define down(i,j,n) for(int i=j;i>=n;i--) #define FILE "spo" const int MAXN=1e5+5; const int oo=0x3f3f3f3f; inline int read(){ char ch=getchar();int x=0,f=1; while(ch>‘9‘||ch<‘0‘){if(ch==‘-‘)f=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘;ch=getchar();} return x*f; } int N,M,LINK[MAXN],len=0,group[MAXN],dfn[MAXN],low[MAXN],dfs_clock=0,stack[MAXN],top=0,group_num=0,du[MAXN],opp[MAXN],LEN=0,Link[MAXN],q[MAXN],head,tail,lable[MAXN]; bool vis[MAXN]; struct edge{int x,y,next;}e[MAXN<<1],E[MAXN<<1]; namespace solution{ inline void insert(int x,int y){e[++len].next=LINK[x];LINK[x]=len;e[len].y=y;e[len].x=x;} inline void Insert(int x,int y){E[++LEN].next=Link[x];Link[x]=LEN;E[LEN].y=y;E[LEN].x=x;} void init(){ N=read();M=read();N<<=1; up(i,1,M){ int x=read()-1,y=read()-1; insert(x,y^1); insert(y,x^1); } } void tarjan(int node){ dfn[node]=low[node]=++dfs_clock; stack[++top]=node;vis[node]=1; for(int i=LINK[node];i;i=e[i].next) if(!dfn[e[i].y]){ tarjan(e[i].y); low[node]=min(low[node],low[e[i].y]); } else if(vis[e[i].y])low[node]=min(low[node],dfn[e[i].y]); if(low[node]==dfn[node]){ int tmp; group_num++; do{ tmp=stack[top--]; vis[tmp]=0; group[tmp]=group_num; }while(tmp!=node); } } void slove(){ memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(group,-1,sizeof(group)); memset(du,0,sizeof(du)); memset(lable,0,sizeof(lable)); up(i,0,N-1)if(!dfn[i])tarjan(i); up(i,0,N-1){ if(group[i]==group[i^1]){ puts("NIE"); exit(0); } opp[group[i]]=group[i^1]; opp[group[i^1]]=group[i]; } up(i,1,len){ int x=e[i].x,y=e[i].y; if(group[x]!=group[y]){ Insert(group[y],group[x]); du[group[x]]++; } } head=1;tail=0; up(i,1,group_num)if(du[i]==0)q[++tail]=i; for(;head<=tail;head++){ int node=q[head]; if(lable[node]!=0)continue; lable[node]=1;lable[opp[node]]=2; for(int i=Link[node];i;i=E[i].next) if(--du[E[i].y]==0) q[++tail]=E[i].y; } } void output(){ up(i,0,N-1)if(lable[group[i]]==1)printf("%d\n",i+1); } } int main(){ //freopen("input.in","r",stdin); freopen(FILE".in","r",stdin); freopen(FILE".out","w",stdout); using namespace solution; init(); slove(); output(); return 0; }
这也是一道很简单的2-SAT题目,相比网络流2-SAT的题目更好看出模型..
这个相比上一题也更加简单,仅判断是否存在方案即可。
如果两个线段是相交的,那么两个线段同时在圆内或者圆外都是相交的。
这样就得到了约束关系,套用2-SAT的模板就行了。
//POJ 3207 //by Cydiater //2016.8.2 #include <iostream> #include <cstdio> #include <cstdlib> #include <cstring> #include <string> #include <algorithm> #include <queue> #include <map> #include <algorithm> #include <iomanip> #include <string> using namespace std; #define ll long long #define up(i,j,n) for(int i=j;i<=n;i++) #define down(i,j,n) for(int i=j;i>=n;i--) const int MAXN=1005; const int oo=0x3f3f3f3f; inline int read(){ char ch=getchar();int x=0,f=1; while(ch>‘9‘||ch<‘0‘){if(ch==‘-‘)f=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘;ch=getchar();} return x*f; } int N,M,LINK[MAXN],len=0,st[MAXN],nd[MAXN],dfn[MAXN],low[MAXN],stack[MAXN],dfs_clock=0,top=0,group[MAXN],group_num=0; bool vis[MAXN]; struct edge{int x,y,next;}e[MAXN<<10]; namespace solution{ inline void insert(int x,int y){e[++len].next=LINK[x];LINK[x]=len;e[len].y=y;e[len].x=x;} void init(){ N=read();M=read(); up(i,0,M-1){ int x=read(),y=read(); st[i]=min(x,y);nd[i]=max(x,y); } } void build(){ up(i,0,M-1)up(j,i+1,M-1){ if((st[i]>st[j]&&st[i]<nd[j]&&nd[i]>nd[j])||(st[j]>st[i]&&st[j]<nd[i]&&nd[j]>nd[i])){ insert(i,j+M); insert(j+M,i); insert(j,i+M); insert(i+M,j); } } } void tarjan(int node){ low[node]=dfn[node]=++dfs_clock; vis[node]=1;stack[++top]=node; for(int i=LINK[node];i;i=e[i].next) if(!dfn[e[i].y]){ tarjan(e[i].y); low[node]=min(low[node],low[e[i].y]); } else if(vis[e[i].y]) low[node]=min(low[node],dfn[e[i].y]); if(low[node]==dfn[node]){ int tmp;group_num++; do{ tmp=stack[top--]; vis[tmp]=0; group[tmp]=group_num; }while(tmp!=node); } } bool check(){ up(i,1,M)if(group[i]==group[i+M])return 0; return 1; } void slove(){ build(); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(vis,0,sizeof(vis)); up(i,1,M<<1)if(!dfn[i])tarjan(i); if(check())puts("panda is telling the truth..."); else puts("the evil panda is lying again"); } } int main(){ //freopen("input.in","r",stdin); using namespace solution; init(); slove(); return 0; }
这道题调了两个多小时....
要注意几点,一是标记的下放,二是邻接表存储时不要打混...邻接表的命名打混这个真心是挺难查的...
题目本身不算难,2-SAT的裸题,拆点建图即可。
还有就是如果对于当前的约束条件,只要这个约束条件是存在的,不管哪个以为着必须的边是否正确,都要连边。
同时要注意到标记的下放。dfs即可。
//POJ 3683 //by Cydiater //2016.8.2 #include <iostream> #include <cstdio> #include <cstdlib> #include <queue> #include <map> #include <algorithm> #include <ctime> #include <map> #include <iomanip> #include <cstring> #include <string> using namespace std; #define ll long long #define up(i,j,n) for(int i=j;i<=n;i++) #define down(i,j,n) for(int i=j;i>=n;i--) const int MAXN=2005; const int oo=0x3f3f3f3f; inline int read(){ char ch=getchar();int x=0,f=1; while(ch>‘9‘||ch<‘0‘){if(ch==‘-‘)f=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘;ch=getchar();} return x*f; } int N,st[MAXN],nd[MAXN],c[MAXN],LINK[MAXN],len=0,dfn[MAXN],low[MAXN],dfs_clock=0,stack[MAXN],top=0,group[MAXN],group_num=0,opp[MAXN],Link[MAXN],LEN=0,du[MAXN],q[MAXN<<5],head=1,tail=0; int lable[MAXN]; bool vis[MAXN]; struct edge{int x,y,next;}e[MAXN*MAXN],E[MAXN*MAXN]; namespace solution{ inline bool check(int st1,int nd1,int st2,int nd2){return (nd1<=st2)||(nd2<=st1);} inline void insert(int x,int y){e[++len].next=LINK[x];LINK[x]=len;e[len].y=y;e[len].x=x;} inline void Insert(int x,int y){E[++LEN].next=Link[x];Link[x]=LEN;E[LEN].y=y;E[LEN].x=x;} inline void print(int a,int b){ if(a<10)printf("0%d:",a); else printf("%d:",a); if(b<10)printf("0%d",b); else printf("%d",b); printf(" "); } void init(){ N=read(); up(i,1,N){ int h1,m1,t1,h2,m2,t2,d; scanf("%d:%d",&h1,&m1); t1=h1*60+m1; scanf("%d:%d",&h2,&m2); t2=h2*60+m2; st[i]=t1;nd[i]=t2;c[i]=read(); } } void build(){ up(i,1,N)up(j,i+1,N){ if(!check(st[i],st[i]+c[i],st[j],st[j]+c[j])){ insert(i,j+N); insert(j,i+N); } if(!check(st[i],st[i]+c[i],nd[j]-c[j],nd[j])){ insert(i,j); insert(j+N,i+N); } if(!check(nd[i]-c[i],nd[i],st[j],st[j]+c[j])){ insert(i+N,j+N); insert(j,i); } if(!check(nd[i]-c[i],nd[i],nd[j]-c[j],nd[j])){ insert(i+N,j); insert(j+N,i); } } } void tarjan(int node){ dfn[node]=low[node]=++dfs_clock; vis[node]=1;stack[++top]=node; for(int i=LINK[node];i;i=e[i].next) if(!dfn[e[i].y]){ tarjan(e[i].y); low[node]=min(low[node],low[e[i].y]); } else if(vis[e[i].y]) low[node]=min(low[node],dfn[e[i].y]); if(low[node]==dfn[node]){ int tmp;group_num++; do{ tmp=stack[top--]; vis[tmp]=0; group[tmp]=group_num; }while(tmp!=node); } } void re_build(){ up(i,1,N){ if(group[i]==group[i+N]){ puts("NO"); exit(0); } opp[group[i]]=group[i+N]; opp[group[i+N]]=group[i]; } puts("YES"); up(i,1,len){ int x=e[i].x,y=e[i].y; if(group[x]==group[y])continue; Insert(group[y],group[x]); du[group[x]]++; } } void downit(int node){ if(lable[node])return; lable[node]=-1; for(int i=Link[node];i;i=E[i].next) downit(E[i].y); } void top_sort(){ head=1;tail=0; up(i,1,group_num)if(du[i]==0)q[++tail]=i; for(;head<=tail;head++){ int node=q[head]; if(lable[node])continue; lable[node]=1;downit(opp[node]); for(int i=Link[node];i;i=E[i].next) if(--du[E[i].y]==0) q[++tail]=E[i].y; } } void slove(){ memset(vis,0,sizeof(vis)); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(du,0,sizeof(du)); memset(lable,0,sizeof(lable)); build(); up(i,1,N<<1)if(!dfn[i])tarjan(i); re_build(); top_sort(); } void output(){ up(i,1,N){ if(lable[group[i]]==1){print(st[i]/60,st[i]%60);printf(" ");print((st[i]+c[i])/60,(st[i]+c[i])%60);} else {print((nd[i]-c[i])/60,(nd[i]-c[i])%60);printf(" ");print(nd[i]/60,nd[i]%60);} printf("\n"); } } } int main(){ freopen("input.in","r",stdin); using namespace solution; init(); slove(); output(); return 0; }
题目大意是给N个点M条边,每个点可以是0或者1,每条边是一个操作,每个一边的操作对应一个值,问是否有可行的方案
2-SAT的模型通常都很好观察出来。
这里的建模需要注意两个点。
1.op==and而且res==1。
这时候A点必须是1,B点也必须是1,如何保证这一点?
if(op==1&&res==1){
insert(x,x+N);
insert(y,y+N);
}
2.op==XOR是不需要连边的。
我没搞懂为什么..但是好像是最后tarjan会自动的把那个xor的情况给处理掉。
//POJ 3678 //by Cydiater //2016.8.3 #include <iostream> #include <cstdio> #include <cstdlib> #include <cmath> #include <ctime> #include <queue> #include <map> #include <algorithm> #include <cstring> #include <string> #include <algorithm> using namespace std; #define ll long long #define up(i,j,n) for(int i=j;i<=n;i++) #define down(i,j,n) for(int i=j;i>=n;i--) const int MAXN=5005; const int oo=0x3f3f3f3f; inline int read(){ char ch=getchar();int x=0,f=1; while(ch>‘9‘||ch<‘0‘){if(ch==‘-‘)f=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘;ch=getchar();} return x*f; } int N,M,LINK[MAXN],len=0,dfn[MAXN],low[MAXN],stack[MAXN],top=0,dfs_clock=0,group_num=0,group[MAXN]; bool vis[MAXN]; string s; map<string,int>m; struct edge{ int x,y,next; }e[MAXN<<10]; namespace solution{ inline void insert(int x,int y){e[++len].next=LINK[x];LINK[x]=len;e[len].y=y;} void init(){ m["AND"]=1;m["OR"]=2;m["XOR"]=3; N=read();M=read(); up(i,1,M){ int x=read(),y=read(),res=read();cin>>s; int op=m[s]; if(op==1&&res==1){ insert(x,x+N); insert(y,y+N); } if(op==1&&res==0){ insert(x+N,y); insert(y+N,x); } if(op==2&&res==1){ insert(x,y+N); insert(y,x+N); } if(op==2&&res==0){ insert(x+N,x); insert(y+N,y); } if(op==3&&res==1){ insert(x,y+N); insert(y,x+N); insert(y+N,x); insert(x+N,y); } if(op==3&&res==0){ insert(x,y); insert(y,x); insert(x+N,y+N); insert(y+N,x+N); } } } void tarjan(int node){ dfn[node]=low[node]=++dfs_clock; stack[++top]=node;vis[node]=1; for(int i=LINK[node];i;i=e[i].next) if(!dfn[e[i].y]){ tarjan(e[i].y); low[node]=min(low[node],low[e[i].y]); }else if(vis[e[i].y]) low[node]=min(low[node],dfn[e[i].y]); if(low[node]==dfn[node]){ group_num++; int tmp; do{ tmp=stack[top--]; vis[tmp]=0; group[tmp]=group_num; }while(tmp!=node); } } bool check(){ up(i,0,N-1)if(group[i]==group[i+N])return 0; return 1; } void slove(){ memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(vis,0,sizeof(vis)); up(i,1,N<<1)if(!dfn[i])tarjan(i); if(check())puts("YES"); else puts("NO"); } } int main(){ //freopen("input.in","r",stdin); using namespace solution; init(); slove(); return 0; }
最后一道2-SAT题目!
首先一些hate和friend的约束条件建图,然后根据二分出来的最长距离,对于i,j之间所有可能的路径:
i-S1-j
i-S2-j
i-S1-S2-j
i-S2-S1-j
如果存在大于lim的,也加入到约束条件中,判一下就行了。
//POJ 2749 //by Cydiater //2016.8.8 #include <iostream> #include <cstdio> #include <cstring> #include <string> #include <algorithm> #include <queue> #include <map> #include <iomanip> #include <ctime> #include <cmath> #include <cstdlib> using namespace std; #define ll long long #define up(i,j,n) for(int i=j;i<=n;i++) #define down(i,j,n) for(int i=j;i>=n;i--) typedef pair<int,int> pii; const int MAXN=2005; const int oo=0x3f3f3f3f; inline int read(){ char ch=getchar();int x=0,f=1; while(ch>‘9‘||ch<‘0‘){if(ch==‘-‘)f=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘){x=x*10+ch-‘0‘;ch=getchar();} return x*f; } pii S1,S2,node[MAXN],hate[MAXN],fri[MAXN]; int N,A,B,leftt,rightt,mid,LINK[MAXN],len,dfn[MAXN],low[MAXN],stack[MAXN],top,dfs_clock,group[MAXN],group_num; bool vis[MAXN]; struct edge{int x,y,next;}e[MAXN<<10]; namespace solution{ inline int dist(pii a,pii b){return abs(a.first-b.first)+abs(a.second-b.second);} inline void insert(int x,int y){e[++len].next=LINK[x];LINK[x]=len;e[len].y=y;e[len].x=x;} void init(){ N=read();A=read();B=read(); int x=read(),y=read(); S1=make_pair(x,y); x=read();y=read(); S2=make_pair(x,y); up(i,1,N){ int x=read(),y=read(); node[i]=make_pair(x,y); } up(i,1,A){ int x=read(),y=read(); hate[i]=make_pair(x,y); } up(i,1,B){ int x=read(),y=read(); fri[i]=make_pair(x,y); } } void tarjan(int node){ stack[++top]=node;vis[node]=1; low[node]=dfn[node]=++dfs_clock; for(int i=LINK[node];i;i=e[i].next) if(!dfn[e[i].y]){ tarjan(e[i].y); low[node]=min(low[node],low[e[i].y]); }else if(vis[e[i].y]) low[node]=min(low[node],dfn[e[i].y]); if(dfn[node]==low[node]){ group_num++;int tmp; do{ tmp=stack[top--]; vis[tmp]=0; group[tmp]=group_num; }while(tmp!=node); } } bool check(int lim){ len=0;top=0;dfs_clock=0;group_num=0; memset(LINK,0,sizeof(LINK)); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); memset(vis,0,sizeof(vis)); up(i,1,A){ int x=hate[i].first,y=hate[i].second; insert(x,y+N); insert(y,x+N); insert(x+N,y); insert(y+N,x); } up(i,1,B){ int x=fri[i].first,y=fri[i].second; insert(x,y); insert(y,x); insert(x+N,y+N); insert(y+N,x+N); } up(i,1,N)up(j,i+1,N){ if(dist(node[i],S1)+dist(S1,node[j])>lim){ insert(i,j+N); insert(j,i+N); } if(dist(node[i],S2)+dist(S2,node[j])>lim){ insert(i+N,j); insert(j+N,i); } if(dist(node[i],S1)+dist(S1,S2)+dist(S2,node[j])>lim){ insert(i,j); insert(j+N,i+N); } if(dist(node[i],S2)+dist(S1,S2)+dist(S1,node[j])>lim){ insert(i+N,j+N); insert(j,i); } } up(i,1,N<<1)if(!dfn[i])tarjan(i); up(i,1,N)if(group[i]==group[i+N])return 0; return 1; } void slove(){ leftt=0;rightt=10000000; while(leftt+1<rightt){ mid=(leftt+rightt)>>1; if(check(mid)) rightt=mid; else leftt=mid; } if((!check(leftt))&&(!check(rightt)))puts("-1"); else if(check(leftt)) printf("%d\n",leftt); else printf("%d\n",rightt); } } int main(){ //freopen("input.in","r",stdin); using namespace solution; init(); slove(); return 0; }