先回顾最裸的2-sat模型
某国有n个党派,每个党派在议会中恰有2个代表。现在要成立和平委员会 ,该会满足:每个党派在和平委员会中有且只有一个代表 如果某两个代表不和,则他们不能都属于委员会 代表的编号从1到2n,编号为2a-1、2a的代表属于第a个党派。
思想: 由于一些人不能同时被选入委员会,那么假设a和b不和,那么当选泽a的时候必选择b’,当选择b的时候必选择a’.那么我们可以根据这个关系建个图。这个图是对称的,由于假如有边a->b’,那么必有b->a’.然后我们在这个图中找到答案,但是假如这个图中有环并且某个党派的的两个状态(人)都在里边,那么说明无解,否则说明有解。
再简化这个模型:就是有n个点,每个点有取与不取两种状态,m组矛盾,每一组矛盾为i和j,则i与jj要么都选要么都不选,ii与j要么都选要么都不选,在i于jj建立有向边,在j与ii建立有向边,这里注意一点:建图的时候是建立有向边,并且是对称建立。这里还有一点,为什么是i-〉jj,j-〉ii而不是ii->jj,jj->i,后面在具体解释。
注意三点:1、两种状态;2、矛盾是什么;3、边的意义
先看简单一些的poj 3207
http://poj.org/problem?id=3207
题目大意是:圆环上n个点编号为0,1,2,3......n-1,连接m组点,连线要么在圆内要么在圆外,判断线是否有相交。
线有两种状态:圆内与圆外,需要判断线是不是相交,可能相交的情况就两种(见代码),这时候建立的是双向边,我是这么理解的:line1,line2同时在圆内会相交,同时在圆外也会相交,那么就意味着如果line1在圆内,那么line2必须在圆外,如果line2在圆外,则line1必在圆内,所以建立双向边,下一个例子poj 3678你会看到并不是所有情况都可以建立双向边。line1在圆内的状态与line2在圆外的状态相连,line1在圆外的状态与line2在圆内的状态相连,最后检查矛盾,某一个line会不会在同一个强联通分量里
贴代码:
#include<cstdio> #include<cstring> #include<algorithm> #include<stack> using namespace std; #define N 1005 int head[N],scnt,cnt,dfn[N],low[N],id[N],n,m,ee[N][2]; struct node{ int to; int next; }edge[500000]; stack<int>st; void addedge(int u,int v,int k) { edge[k].to=v; edge[k].next=head[u]; head[u]=k; } void tarjan(int u) { int v,k,min1; min1=dfn[u]=low[u]=cnt++; st.push(u); for(k=head[u];k!=-1;k=edge[k].next) { v=edge[k].to; if(dfn[v]==-1)tarjan(v); min1=min(min1,low[v]); } if(min1<low[u]){low[u]=min1;return;} do { id[v=st.top()]=scnt; st.pop(); low[v]=n; }while(v!=u); scnt++; } void init() { memset(dfn,-1,sizeof(dfn)); memset(head,-1,sizeof(head)); scnt=cnt=0; } int main() { int i,j,num; scanf("%d%d",&n,&m); init(); for(i=0;i<m;i++) { scanf("%d%d",&ee[i][0],&ee[i][1]); if(ee[i][0]>ee[i][1])swap(ee[i][0],ee[i][1]); } num=0; for(i=0;i<m;i++) for(j=i+1;j<m;j++) { if(ee[i][0]<ee[j][0]&&ee[j][0]<ee[i][1]&&ee[i][1]<ee[j][1]||ee[i][0]>ee[j][0]&&ee[i][0]<ee[j][1]&&ee[j][1]<ee[i][1]) { addedge(i,j+m,num++); addedge(j+m,i,num++); addedge(j,i+m,num++); addedge(i+m,j,num++); } } for(i=0;i<m*2;i++) if(dfn[i]==-1)tarjan(i); bool flag=true; for(i=0;i<m;i++) { if(id[i]==id[i+m]){flag=false;break;} } if(flag)printf("panda is telling the truth...\n"); else printf("the evil panda is lying again\n"); return 0; }
题目似乎难以看懂,主要是那个万恶的表格难以看懂...
表格按高中学得表格看吧,其实就跟计算机本来的&,|,^是一样的,看不懂也没关系:
建图:
先找两种状态:第i个点是 0和1
再找矛盾:比如i&j=0,建立边i=1->j=0,j=1->i=0,这时候千万别写成双向边,因为i&j=0,只能说明如果i为1,j必须为0,不能说明如果j为0,i 必须为1(i取0也可以),
注意不同。
比较难想的一点是,i&j=1,怎么建图,答案是建立边i=0->i=1,j=0->j=1
为什么?
看边的意义:i->j意味着如果i装态,则必有j状态,好了,i=0->i=1就意味着,i必定为1了
检查矛盾的方式还是那样,看矛盾的状态是不是在同一个强联通分量里,其实做了缩点处理
贴代码:
#include<cstdio> #include<cstring> #include<algorithm> #include<stack> #include<cstring> using namespace std; #define N 2100 #define M 1000005 int head[N],dfn[N],low[N],id[N]; /*最后再来检查数组范围是不是够吧*/ int n,m,cnt,scnt; stack<int>st; struct node{ int to,next; }edge[M*7]; void addedge(int u,int v,int k) { edge[k].to=v; edge[k].next=head[u]; head[u]=k; } void tarjan(int u) { int v,i,min1=dfn[u]=low[u]=cnt++; st.push(u); for(i=head[u];i!=-1;i=edge[i].next) { v=edge[i].to; if(dfn[v]==-1)tarjan(v); min1=min(min1,low[v]); } if(min1<low[u]){low[u]=min1;return;} do { id[v=st.top()]=scnt; st.pop(); low[v]=n*2; }while(v!=u); scnt++; } void init() { memset(dfn,-1,sizeof(dfn)); memset(id,-1,sizeof(id)); memset(head,-1,sizeof(head)); scnt=cnt=0; } int main() { int i,j,a,b,c; char op[4]; scanf("%d%d",&n,&m); init(); int num=0; for(i=0;i<m;i++) { scanf("%d%d%d%s",&a,&b,&c,op); if(!strcmp(op,"AND")) { if(c==1) { addedge(a,a+n,num++); addedge(b,b+n,num++); addedge(a+n,b+n,num++); addedge(b+n,a+n,num++); } else { addedge(a+n,b,num++); addedge(b+n,a,num++); } } else if(!strcmp(op,"OR")) { if(c==0) { addedge(a+n,a,num++); addedge(b+n,b,num++); addedge(a,b,num++); addedge(b,a,num++); } else { addedge(a,b+n,num++); addedge(b,a+n,num++); } } else if(!strcmp(op,"XOR")) { if(c==0) { addedge(a,b,num++); addedge(b,a,num++); addedge(a+n,b+n,num++); addedge(b+n,a+n,num++); } else { addedge(a,b+n,num++); addedge(b+n,a,num++); addedge(a+n,b,num++); addedge(b,a+n,num++); } } } int flag=1; for(i=0;i<n*2;i++) if(dfn[i]==-1)tarjan(i); for(i=0;i<n;i++) { if(id[i]==id[i+n]&&id[i]!=-1) { flag=0; break; } } if(flag)printf("YES\n"); else printf("NO\n"); while(!st.empty())st.pop(); return 0; }