zoukankan      html  css  js  c++  java
  • 2-Sat专题

    先推两篇dalao资料:

    https://wenku.baidu.com/view/afd6c436a32d7375a41780f2.html

    https://blog.csdn.net/jarjingx/article/details/8521690

    好像还有https://wenku.baidu.com/view/0f96c3daa58da0116c1749bc.html,不过我还没有看过

    花了几天搞了搞这个东西

    写一点小小的心得体会

    由于本人是蒟蒻,而且这篇小笔记其实是留给自己看的,所以可能不会用什么高深的话去解释这个东西,也不会讲的非常详细,如果看完那两位大佬的博文还不是很懂,或许可以再看一看我的这篇笔记,希望能有所帮助

    一、概念

    至于2-Sat,它是解决一个类给了你一个图中一大坨关系,然后让你判断这些关系同时成立的图是否存在这样一类问题,为什么研究2-sat呢?无可置疑的是它很有用。为什么不研究3-sat乃至k更大的情况呢,因为它们已经被证明为是 NP完全问题了,在更多的情况下,2-sat不仅仅是元素个数最多的集合含有2个元素,而是每个集合都含有2个元素,且这2个元素不允许同时取出,这篇文章主要讨论的就是这样一种特殊的模型。这类题都是比较裸的,看一眼题就能知道要用2-Sat,所以可能并没有很多比赛考过这个东西。

    二、算法流程&&说明

    捋一遍2-sat方法流程

    1.根据题意建图

    连接若取a就必须取b这些路径

    若条件是不能取a,你可以让a连到a’,矛盾即可

    不懂为什么有人奇怪要建反向边,不是不建

    2.Tarjan缩点

    没什么好说的

    缩起来的这一个点表示在这个环里面任取一个点就要取完这一个环,其实这是为了可以在图中进行拓扑排序和判断是否可行

    3.判断是否存在

    这个,嗯,如果有两个对应点在一个环里,就说明若取a必取a',矛盾

    如果没有,就说明存在这样一个满足条件的图

    4.拓扑排序

    Tarjan缩点是个反拓扑排序,不必再排

    5.根据拓扑排序输出方案

    这个东西想了很久,发现自己过于zz,其实利用对称性即可证明这个东西,当a的拓扑序大于a'时,只有这两种情况:

    1)两者有路径相连,即若取a'则必取a,故不能取a',只可取a

    2)两者无路径相连,对称性这个东西其实那两位大佬都讲的很清楚,不再赘述,就直接引用一下两个结论:

    由于点对称,边对称,则图对称,所以设a点有x个前序节点,有y个后序节点,则a’点有y个前序节点,有x个后序节点,而且互相对应

    明确了这一点,其实就不难证明了,只需要将和a有关的所有节点通通取走,对应的通通不取,其实对其他的节点没有任何干扰,其他节点也只会出现这两种情况,所以可以简要证明拓扑排序的正确性

    6.注意

    能够保证其对称性才能用2-Sat

     至于没有对称性,我并不会严格证明2-Sat的正确性,例如Priest John's Busiest Day这样一道题,我用对称性和用玄学半对称性分别打了一遍,貌似都过了样例,不过还是保障对称性这个东西比较好

    三、模板

    洛谷P4782 【模板】2-SAT 问题

    题目背景

    2-SAT 问题 模板

    输入格式:

     第一行两个整数n和m,意义如体面所述。

    接下来m行每行4个整数 i a j b,表示“xi为a或xj为b”(a,b∈{0,1})

    输出格式:

     如无解,输出“IMPOSSIBLE”(不带引号); 否则输出"POSSIBLE"(不带引号),下 一行n个整数x1~xnxi∈{0,1}),表示构造出的解。

    输入输出样例

    输入样例#1: 
    3 1
    1 1 3 0
    输出样例#1: 
    POSSIBLE
    0 0 0

    若取-a则必须取b
    若取-b则必须取a
    剩下的套个板子即可
    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define rep(i,a,b) for(int i=a;i<=b;i++)
    #define MAXN 2004000
    #define MAXM 4000500
    #define int long long
    using namespace std;
    int n,m,to[MAXM],nxt[MAXM],fir[MAXN],tot;
    int work(int a,int b){
        if(b==1) return a+n;
        else return a;
    }
    void ade(int u,int v){
        //cout<<u<<" "<<v<<"DFDFDF"<<endl;
        to[++tot]=v;
        nxt[tot]=fir[u];
        fir[u]=tot;
    }
    int dfn[MAXN],low[MAXN],col[MAXN],cnt,sta[MAXN],top,num;
    void Tarjan(int x){
        dfn[x]=low[x]=++num;
        sta[++top]=x;
        for(int k=fir[x];k;k=nxt[k]){
            if(!dfn[to[k]]){
                Tarjan(to[k]);
                low[x]=min(low[x],low[to[k]]);
            }
            else if(!col[to[k]]) low[x]=min(low[x],dfn[to[k]]);
        }
        if(low[x]==dfn[x]){
            col[x]=++cnt;
            while(sta[top]!=x) col[sta[top]]=cnt,--top;
            --top;
        }
        //printf("%d %d %d
    ",x,dfn[x],low[x]);
    }
    bool twosat(){
        rep(i,1,2*n){
            if(!dfn[i]) Tarjan(i);
        }
        rep(i,1,n) if(col[i]==col[i+n]) return 0;
        return 1;
    }
    signed main(){
        scanf("%lld%lld",&n,&m);
        rep(i,1,m){
            int a,b,c,d;
            scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
            ade(work(a,1-b),work(c,d)); ade(work(c,1-d),work(a,b));
        }
        if(twosat()){
            printf("POSSIBLE
    ");
            rep(i,1,n) printf("%d ",col[i]>col[i+n]); //如果不在一个连同图内,任取一个即可,不然,一定取拓扑徐靠后的,由于Tarjan是反拓扑徐,所以应为col[i]>col[i+n]
        }
        else printf("IMPOSSIBLE");
        return 0;
    }

     四、一些题目

      这些题目其实和板子区别不大

     一、和平委员会

    题目描述

    原题来自:POI 2001

    根据宪法,Byteland 民主共和国的公众和平委员会应该在国会中通过立法程序来创立。 不幸的是,由于某些党派代表之间的不和睦而使得这件事存在障碍。

    此委员会必须满足下列条件:

    • 每个党派都在委员会中恰有 1 个代表,

    • 如果 2 个代表彼此厌恶,则他们不能都属于委员会。

    每个党在议会中有 2个代表。代表从 1 编号到 2n。 编号为 2i1 和 2i 的代表属于第 i 个党派。

    任务:写一程序读入党派的数量和关系不友好的代表对,计算决定建立和平委员会是否可能,若行,则列出委员会的成员表。

    输入格式

    第一行有两个非负整数 n 和 m。他们各自表示:党派的数量 n 和不友好的代表对 m。 接下来 m 行,每行为一对整数 a,b,表示代表 a,b 互相厌恶。

    输出格式

    如果不能创立委员会,则输出信息NIE。若能够成立,则输出包括 n 个从区间 1 到 2n 选出的整数,按升序写出,每行一个,这些数字为委员会中代表的编号。

    如果委员会能以多种方法形成,程序可以只输出它们的某一个。

    样例

    样例输入

    3 2
    1 3
    2 4

    样例输出

    1
    4
    5

    数据范围与提示

    1n8000,0m20000,1a<b2n

    嗯,其实只要BB一下建图方案

    若取a则必取-b,若取b则必取-a

     1 #include<iostream>
     2 #include<cstdio>
     3 #include<cstring>
     4 #include<algorithm>
     5 #define rep(i,a,b) for(int i=a;i<=b;i++)
     6 using namespace std;
     7 int n,m,to[40050],nxt[40050],fir[17000],tot;
     8 int dfn[17000],low[17000],num,cnt,col[17000],sta[17000],top;
     9 void ade(int u,int v){
    10     to[++tot]=v;
    11     nxt[tot]=fir[u];
    12     fir[u]=tot;
    13 }
    14 void Tarjan(int x){
    15     dfn[x]=low[x]=++num;
    16     sta[++top]=x;
    17     for(int k=fir[x];k;k=nxt[k]){
    18         if(!dfn[to[k]]){Tarjan(to[k]); low[x]=min(low[to[k]],low[x]);}
    19         else if(!col[to[k]]) low[x]=min(low[x],dfn[to[k]]);
    20     }
    21     if(low[x]==dfn[x]){
    22         col[x]=++cnt;
    23         while(sta[top]!=x) col[sta[top]]=cnt,--top;
    24         --top;
    25     }
    26 }
    27 bool twosat(){
    28     rep(i,1,2*n) if(!dfn[i]) Tarjan(i);
    29     rep(i,1,n) if(col[i*2]==col[i*2-1]) return 0;
    30     
    31     return 1;
    32 }
    33 int main(){
    34     scanf("%d%d",&n,&m);
    35     rep(i,1,m){
    36         int a,b,c=1,d=1; scanf("%d%d",&a,&b);
    37         if(a&1) c=-1; if(b&1) d=-1;
    38         ade(a,b-d); ade(b,a-c);
    39     }
    40     if(twosat()) rep(i,1,n) if(col[i*2]>col[i*2-1]) printf("%d
    ",i*2-1);else printf("%d
    ",i*2);
    41     else printf("NIE");
    42 }

     二、Katu Puzzle

    Description

    Katu Puzzle is presented as a directed graph G(VE) with each edge e(a, b) labeled by a boolean operator op (one of AND, OR, XOR) and an integer c (0 ≤ c ≤ 1). One Katu is solvable if one can find each vertex Vi a value Xi (0 ≤ X≤ 1) such that for each edge e(a, b) labeled by op and c, the following formula holds:

     Xa op Xb = c

    The calculating rules are:

    AND 0 1
    0 0 0
    1 0 1
    OR 0 1
    0 0 1
    1 1 1
    XOR 0 1
    0 0 1
    1 1 0

    Given a Katu Puzzle, your task is to determine whether it is solvable.

    Input

    The first line contains two integers N (1 ≤ N ≤ 1000) and M,(0 ≤ M ≤ 1,000,000) indicating the number of vertices and edges.
    The following M lines contain three integers (0 ≤ a < N), b(0 ≤ b < N), c and an operator op each, describing the edges.

    Output

    Output a line containing "YES" or "NO".

    Sample Input

    4 4
    0 1 1 AND
    1 2 1 OR
    3 2 0 AND
    3 0 0 XOR

    Sample Output

    YES

    Hint

    X0 = 1, X1 = 1, X2 = 0, X3 = 1.

    建图方案:

    1.a and b=1  连-a->a,-b->b,使前者的拓扑序恒大于后者即可

    2.a and b=0 连a->-b,b->-a

    3.a or b=1 连-a->b,-b->a

    4.a or b=0 连 a->-a,b->-b

    5.a xor b=0 懒得写,见代码

    6.类似上

    //建图方案见代码 
    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #define rep(i,a,b) for(int i=a;i<=b;i++)
    using namespace std;
    int n,m,to[4000500],nxt[4000500],fir[2050],tot;
    int dfn[2050],low[2050],sta[2050],col[2050],num,top,cnt;
    char t[10];
    int a,b,c;
    void ade(int u,int v){
        to[++tot]=v;
        nxt[tot]=fir[u];
        fir[u]=tot;
    }
    void Tarjan(int x){
        dfn[x]=low[x]=++num;
        sta[++top]=x;
        for(int k=fir[x];k;k=nxt[k]){
            if(!dfn[to[k]]) Tarjan(to[k]),low[x]=min(low[x],low[to[k]]);
            else if(!col[to[k]]) low[x]=min(low[x],dfn[to[k]]);
        } 
        if(low[x]==dfn[x]){
            col[x]=++cnt;
            while(sta[top]!=x) col[sta[top]]=cnt,--top;--top;
        }
    }
    bool twosat(){
        rep(i,0,2*n-1) if(!dfn[i]) Tarjan(i);
        rep(i,0,n-1) if(col[i]==col[i+n]) return 0;
        return 1;
    }
    void add_and(int x,int y,int z){
        if(z==1) ade(x,x+n),ade(y,y+n);
        else ade(x+n,y),ade(y+n,x);
    }
    void add_or(int x,int y,int z){
        if(z==1) ade(x,y+n),ade(y,x+n);
        else ade(x+n,x),ade(y+n,y);
    }
    void add_xor(int x,int y,int z){
        if(z==1) ade(x,y+n),ade(y,x+n),ade(x+n,y),ade(y+n,x);
        else ade(x,y),ade(x+n,y+n),ade(y+n,x+n),ade(y,x);
    }
    int main(){
        scanf("%d%d",&n,&m);
        rep(i,1,m){
            scanf("%d%d%d%s",&a,&b,&c,t);
            if(t[0]=='X') add_xor(a,b,c);
            else if(t[0]=='A') add_and(a,b,c);
            else add_or(a,b,c);
        }
        if(twosat()) printf("YES");
        else printf("NO");
        return 0;
    }

    三、Priest John's Busiest Day

     

    Description

    John is the only priest in his town. September 1st is the John's busiest day in a year because there is an old legend in the town that the couple who get married on that day will be forever blessed by the God of Love. This year N couples plan to get married on the blessed day. The i-th couple plan to hold their wedding from time Si to time Ti. According to the traditions in the town, there must be a special ceremony on which the couple stand before the priest and accept blessings. The i-th couple need Di minutes to finish this ceremony. Moreover, this ceremony must be either at the beginning or the ending of the wedding (i.e. it must be either from Si to Si + Di, or from Ti - Di to Ti). Could you tell John how to arrange his schedule so that he can present at every special ceremonies of the weddings.

    Note that John can not be present at two weddings simultaneously.

    Input

    The first line contains a integer N ( 1 ≤ N ≤ 1000). 
    The next N lines contain the SiTi and DiSi and Ti are in the format of hh:mm.

    Output

    The first line of output contains "YES" or "NO" indicating whether John can be present at every special ceremony. If it is "YES", output another N lines describing the staring time and finishing time of all the ceremonies.

    Sample Input

    2
    08:00 09:00 30
    08:15 09:00 20
    
    

    Sample Output

    YES
    08:00 08:30
    08:40 09:00

    思路:这道题是一道毒瘤题

    使婚礼开始时和婚礼结束时的分别为0和1,然后如果第i个婚礼开始时不能与第j个婚礼开始时冲突,即第i个婚礼开始时举办仪式就不能在第j个婚礼开始时举办仪式,连i~j+n这条边

    其实两个都不能参加可以连a->-a,也可以连a->b,a->-b,不过后者可以保障其对称性,还是建议使用后者

    以此类推

    枚举每个i,j,O(n^2)连边,然后就变成了2-Sat的板子,跑一边模板即可

    代码两篇(前者是第一种连边方案,后者是第二种连边方案):

    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    #define rep(i,a,b) for(int i=a;i<=b;i++)
    using namespace std;
    int n,tot,fir[2050],a[1050],b[1050],t[1050],nxt[4050000],to[4005000];
    int dfn[2050],low[2050],num,cnt,sta[2050],top,col[2050];
    bool book[2050];
    int read(){
    	int x=0;
    	char ch=getchar();
    	while('0'>ch || ch>'9') ch=getchar();
    	while('0'<=ch && ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
    	return x;
    }
    int reads(){
    	int x=read(),y=read();
    	return x*60+y;
    }
    void ade(int u,int v){
    	to[++tot]=v;
    	nxt[tot]=fir[u];
    	fir[u]=tot;
    }
    void Tarjan(int x){
    	dfn[x]=low[x]=++num;
    	sta[++top]=x;
    	for(int k=fir[x];k;k=nxt[k]){
    		if(!dfn[to[k]]) Tarjan(to[k]),low[x]=min(low[x],low[to[k]]);
    		else if(!col[to[k]]) low[x]=min(low[x],dfn[to[k]]);
    	} 
    	if(low[x]==dfn[x]){
    		col[x]=++cnt;
    		while(sta[top]!=x) col[sta[top]]=cnt,--top;--top;
    	}
    }
    bool check(int x,int y,int g,int h){
    	if(x<=g && g<y) return 0;
    	if(x<h && h<=y) return 0;
    	if(g<=x && x<h) return 0;
    	if(g<y && y<=h) return 0;
    	return 1;
    }
    bool twosat(){
    	rep(i,1,2*n) if(!dfn[i]) Tarjan(i);
    	rep(i,1,n) if(col[i]==col[i+n]) return 0;
    	return 1;
    }
    void work(int x){
    	printf("%02d:%02d",x/60,x%60);
    }
    void init(){
    	num=0;
    	tot=0;
    	top=0;
    	cnt=0;
    	memset(dfn,0,sizeof(dfn));
    	memset(low,0,sizeof(low));
    	memset(col,0,sizeof(col));
    	memset(fir,0,sizeof(fir));
    	memset(book,0,sizeof(book));
    }
    int main(){
    	while(~scanf("%lld",&n)){
    		init();
    		rep(i,1,n) a[i]=reads(),b[i]=reads(),t[i]=read();
        	rep(i,1,n){
        		rep(j,1,n){
        			if(i==j) continue;
        			bool x=0,y=0;
        			if(check(a[i],a[i]+t[i],a[j],a[j]+t[j])) x=1;
        			if(check(a[i],a[i]+t[i],b[j]-t[j],b[j])) y=1;
        			if(x && !y) ade(i,j);
         	     	else if(y && !x) ade(i,j+n);
        		    else if(!y && !x && !book[i]) ade(i,i+n),book[i]=1;
        		    x=0,y=0;
        			if(check(b[i]-t[i],b[i],a[j],a[j]+t[j])) x=1;
        			if(check(b[i]-t[i],b[i],b[j]-t[j],b[j])) y=1;
       			    if(x && !y) ade(i+n,j);
        	     	else if(y && !x) ade(i+n,j+n);
        		    else if(!y && !x && !book[i+n]) ade(i+n,i),book[i+n]=1;
        		}
        	}
        	if(twosat()){
            	printf("YES
    ");
            	rep(i,1,n){
            		if(col[i]<col[i+n]){
            			work(a[i]);cout<<" ";work(a[i]+t[i]);puts("");
    	    		}
    	    		else{
    	    			work(b[i]-t[i]);cout<<" "; work(b[i]);puts("");
    	    		}
    	    	}
        	}
        	else printf("NO
    ");
    	}
    	
    }
    #include<iostream>
    #include<cstring>
    #include<cstdio>
    #include<algorithm>
    #define rep(i,a,b) for(int i=a;i<=b;i++)
    using namespace std;
    int n,tot,fir[2050],a[1050],b[1050],t[1050],nxt[4050000],to[4005000];
    int dfn[2050],low[2050],num,cnt,sta[2050],top,col[2050];
    bool book[2050];
    int read(){
    	int x=0;
    	char ch=getchar();
    	while('0'>ch || ch>'9') ch=getchar();
    	while('0'<=ch && ch<='9') x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
    	return x;
    }
    int reads(){
    	int x=read(),y=read();
    	return x*60+y;
    }
    void ade(int u,int v){
    	to[++tot]=v;
    	nxt[tot]=fir[u];
    	fir[u]=tot;
    }
    void Tarjan(int x){
    	dfn[x]=low[x]=++num;
    	sta[++top]=x;
    	for(int k=fir[x];k;k=nxt[k]){
    		if(!dfn[to[k]]) Tarjan(to[k]),low[x]=min(low[x],low[to[k]]);
    		else if(!col[to[k]]) low[x]=min(low[x],dfn[to[k]]);
    	} 
    	if(low[x]==dfn[x]){
    		col[x]=++cnt;
    		while(sta[top]!=x) col[sta[top]]=cnt,--top;--top;
    	}
    }
    bool check(int x,int y,int g,int h){
    	if(x>=h || g>=y) return 1;
    	return 0;
    }
    bool twosat(){
    	rep(i,1,2*n) if(!dfn[i]) Tarjan(i);
    	rep(i,1,n) if(col[i]==col[i+n]) return 0;
    	return 1;
    }
    void work(int x){printf("%02d:%02d",x/60,x%60);}
    void init(){
    	num=0;
    	tot=0;
    	top=0;
    	cnt=0;
    	memset(dfn,0,sizeof(dfn));
    	memset(low,0,sizeof(low));
    	memset(col,0,sizeof(col));
    	memset(fir,0,sizeof(fir));
    	memset(book,0,sizeof(book));
    }
    int main(){
    	while(~scanf("%lld",&n)){
    		init();
    		rep(i,1,n) a[i]=reads(),b[i]=reads(),t[i]=read();
        	rep(i,1,n){
        		rep(j,1,n){
        			if(i==j) continue;
        			bool x=0,y=0;
        			if(check(a[i],a[i]+t[i],a[j],a[j]+t[j])) x=1;
        			if(check(a[i],a[i]+t[i],b[j]-t[j],b[j])) y=1;
        			if(!y) ade(i,j);
         	     	if(!x) ade(i,j+n);
        		    x=0,y=0;
        			if(check(b[i]-t[i],b[i],a[j],a[j]+t[j])) x=1;
        			if(check(b[i]-t[i],b[i],b[j]-t[j],b[j])) y=1;
       			    if(!y) ade(i+n,j);
        	     	if(!x) ade(i+n,j+n);
        		}
        	}
        	if(twosat()){
            	printf("YES
    ");
            	rep(i,1,n){
            		if(col[i]<col[i+n]){work(a[i]); cout<<" "; work(a[i]+t[i]); puts("");}
    	    		else{work(b[i]-t[i]); cout<<" "; work(b[i]); puts("");}
    	    	}
        	}
        	else printf("NO
    ");
    	}
    	
    }
    
  • 相关阅读:
    机器学习: 基于MRF和CNN的图像合成
    概率论经典问题 —— 三个事件 A、B、C 独立 ≠ 三个事件两两独立
    概率论经典问题 —— 三个事件 A、B、C 独立 ≠ 三个事件两两独立
    中国文化史
    中国文化史
    详解第一个CUDA程序kernel.cu
    【读书笔记】 —— 历史篇
    【读书笔记】 —— 历史篇
    美国政府、部门构成及其运作
    实时抢占补丁概观(待续)
  • 原文地址:https://www.cnblogs.com/handsome-zlk/p/10160392.html
Copyright © 2011-2022 走看看