[专题总结]2-sat及题目&题解(3/5 complete)

啥啥啥2-sat今天就是最后一天了???我才打两道题啊。。。

%%%yxm永远领先全世界。。。

为了防止学=没学所以还是要记一下,防止忘也确认自己真正理解了吧。

2-sat是指2适应性问题,然而知道这个没有什么用。

k-sat在k>2时都没有多项式复杂度解法,然而这和你也没关系。

它所解决的问题是,有n个变量,每个变量都有0/1两种取值,给出若干限制条件,形如”若x的取值为0/1,那么y的取值为0/1“

求是否存在合法方案,并且有时要求构造一组可行解

判定是否存在有解其实挺简单的。

前置知识:

  • tarjan求强联通分量(板子,啊,板子。。。)

我们对于每一个变量的两种取值各开一个点,每一种限制就是一条有向边。

时刻记住:有向边a->b的含义是如果选了a那么必须选b。而且这种关系有传递性。

然后在建出的图里跑tarjan求强联通分量。如果某一个变量对应的两个点在同一个强联通分量里,那么根据有向边的含义。

那么这两个点必须都选,与题意不符,所以无解。否则就有解。

构造方案要麻烦一些。

一种麻烦但是好理解的方法是,在缩完scc的DAG里跑拓扑,依次确定每一个scc内点的赋值,同时把对应点所在的scc直接ban掉。

(即赋上相反的值)

最后检查每个点所属的scc的赋值就知道变量的取值了。

而第二种方法更好写一点。只需要枚举每一个变量,选取它的两个取值所在的scc中编号较小的一个的取值即可。

T1:和平委员会

Description:

原题来自:POI 2001

根据宪法,Byteland 民主共和国的公众和平委员会应该在国会中通过立法程序来创立。

不幸的是,由于某些党派代表之间的不和睦而使得这件事存在障碍。

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

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

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

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

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

n<=8000,m<=20000

Solution:

挺好的板子,需要构造方案。

给出两种构造的代码。

 1 #include<cstdio>
 2 #include<iostream>
 3 using namespace std;
 4 int n,m,dfn[16005],tim,low[16005],fir[16005],l[40005],to[40005],cnt;
 5 int sta[40005],top,ins[40005],bel[40005],cnt_scc,al[40005];
 6 void link(int a,int b){l[++cnt]=fir[a];fir[a]=cnt;to[cnt]=b;}
 7 int _fir[16005],_l[40005],_to[40005],_cnt,deg[16005],q[16005],qt,val[16005];
 8 int opp[16005];
 9 void _link(int a,int b){_l[++_cnt]=_fir[a];_fir[a]=_cnt;_to[_cnt]=b;deg[b]++;}
10 void tarjan(int p){
11     dfn[p]=low[p]=++tim;sta[++top]=p;ins[p]=1;
12     for(int i=fir[p];i;i=l[i])if(!dfn[to[i]])tarjan(to[i]),low[p]=min(low[p],low[to[i]]);
13         else if(ins[to[i]])low[p]=min(low[p],dfn[to[i]]);
14     if(low[p]==dfn[p]){
15         cnt_scc++;
16         do{ins[sta[top]]=0;bel[sta[top]]=cnt_scc;top--;}while(sta[top+1]!=p);
17     }
18 }
19 int main(){
20     scanf("%d%d",&n,&m);n<<=1;
21     for(int i=1,x,y;i<=m;++i)scanf("%d%d",&x,&y),link(x+1,y+1^1),link(y+1,x+1^1);
22     for(int i=2;i<=n+1;++i)if(!dfn[i])tarjan(i);
23     for(int i=2;i<=n;i+=2)if(bel[i]==bel[i^1]){puts("NIE");return 0;}
24     for(int i=2;i<=n+1;++i)for(int j=fir[i];j;j=l[j])if(bel[i]!=bel[to[j]])_link(bel[to[j]],bel[i]);
25     for(int i=2;i<=n+1;++i)opp[bel[i]]=bel[i^1];
26     for(int i=1;i<=cnt_scc;++i)if(!deg[i])q[++qt]=i;
27     for(int i=1;i<=cnt_scc;++i)val[i]=-1;
28     for(int qh=1;qh<=qt;++qh){
29         if(val[q[qh]]==-1)val[q[qh]]=0,val[opp[q[qh]]]=1;
30         for(int i=_fir[q[qh]];i;i=_l[i]){deg[_to[i]]--;if(!deg[_to[i]])q[++qt]=_to[i];}
31     }//printf("cnt_scc:%d
",cnt_scc);
32     for(int i=2;i<=n;i+=2)if(val[bel[i]])printf("%d
",(i^1)-1);else printf("%d
",i-1);
33 }
第一种
 1 #include<cstdio>
 2 #include<iostream>
 3 using namespace std;
 4 int n,m,dfn[16005],tim,low[16005],fir[16005],l[40005],to[40005],cnt;
 5 int sta[40005],top,ins[40005],bel[40005],cnt_scc,al[40005];
 6 void link(int a,int b){l[++cnt]=fir[a];fir[a]=cnt;to[cnt]=b;}
 7 void tarjan(int p){
 8     dfn[p]=low[p]=++tim;sta[++top]=p;ins[p]=1;
 9     for(int i=fir[p];i;i=l[i])if(!dfn[to[i]])tarjan(to[i]),low[p]=min(low[p],low[to[i]]);
10         else if(ins[to[i]])low[p]=min(low[p],dfn[to[i]]);
11     if(low[p]==dfn[p]){
12         cnt_scc++;
13         do{ins[sta[top]]=0;bel[sta[top]]=cnt_scc;top--;}while(sta[top+1]!=p);
14     }
15 }
16 int main(){
17     scanf("%d%d",&n,&m);n<<=1;
18     for(int i=1,x,y;i<=m;++i)scanf("%d%d",&x,&y),link(x+1,y+1^1),link(y+1,x+1^1);
19     for(int i=2;i<=n+1;++i)if(!dfn[i])tarjan(i);
20     for(int i=2;i<=n;i+=2)if(bel[i]==bel[i^1]){puts("NIE");return 0;}
21     for(int i=2;i<=n;i+=2)printf("%d
",(i^bel[i]>bel[i^1])-1);
22 }
第二种(简单一些)

T2:编码

Description

原题:NEERC 2016 B. Binary Code

Bob 最新学习了一下二进制前缀编码的那一套理论。二进制编码是指一个由n个互不相同的二进制串s构成的集合.

而如果一套编码理论满足,对于任意的i,j si不是sj的前缀,那么我们称它为前缀编码。

Bob 发现了一张上面写有n行二进制编码的纸,但这张纸年代久远,有些字迹已经模糊不清。

幸运的是,每一行至多只会有一个模糊的字符。

Bob 想知道这n行二进制编码是否有可能是一个前缀编码?

n<=500000 总串长<=500000

Solution:

没想到T2就这么难。

但是真的是好题,所谓前缀优化建边的思路很好啊。

暴力的思路还是比较简单的,建01trie,每个串都把问号当成0/1分别插入。(没问号的插两遍,一样的)

然后每个点都向子树内的所有点的对立点连边,所有点向祖先链上的所有点的对立点连边。

但是这么做的话边数可能会达到$O(n^2)$级别,肯定不可过。

现在我们就需要利用连边关系的传递性了。

既然是想祖先链和子树全连上,直接连和间接连没有区别。

所以我们建一个上行trie,一个下行trie。

然后每个点向trie上的对应点连边(称为发出信号),trie上的节点向每个点的对立点连边(称为接受信号)。

这样就实现了父子之间信息的传递。

然而会出锅,每个点与对立点出环了。。。

这样的话,其实你没有必要自己向自己传递信息吧。

所以你只要在上行trie里你是要给祖先传递信息,那么你把你的信号发出边指向父亲而不是指向自己所在的点是完全没有影响的。

同理,在下行trie里你是要接受来自祖先的信息,所以把你的信号接受边从父亲指过来就可以。

这样的话会有一个锅,就是如果trie的一个节点上堆了多个串,那么这些串之间的信息交流就没有被完成。

那么其实只需要抛弃原trie,建一个新trie,强行规定这些重叠串之间的父子关系,然后就可做了。

貌似也可以map映射一下什么的,没有打不知道。

思路很棒。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 #include<vector>
 5 using namespace std;
 6 vector<int>v[2000005];
 7 int fir[3000005],l[8000005],to[8000005],ecnt,n,tim,dfn[3000005],low[3000005];char s[500005];
 8 int sta[3000005],top,ins[3000005],bel[3000005],cnt_scc,cnt,trie[500005][2],rt,ex;
 9 void link(int a,int b){l[++ecnt]=fir[a];fir[a]=ecnt;to[ecnt]=b;}
10 int oppo(int x){return x&1?x+1:x-1;}
11 void tarjan(int p,int fa){
12     dfn[p]=low[p]=++tim;sta[++top]=p;ins[p]=1;
13     for(int i=fir[p];i;i=l[i])if(to[i]!=fa)if(!dfn[to[i]])tarjan(to[i],p),low[p]=min(low[p],low[to[i]]);
14         else if(ins[to[i]])low[p]=min(low[p],dfn[to[i]]);
15     if(dfn[p]==low[p]){
16         cnt_scc++;
17         do{bel[sta[top]]=cnt_scc;ins[sta[top--]]=0;}while(sta[top+1]!=p);
18     }
19 }
20 void build(int &p,int al,int len,int val){
21     if(!p)p=++cnt;
22     if(al==len){v[p].push_back(val);return;}
23     build(trie[p][s[al]=='1'],al+1,len,val);
24 }
25 void dfs(int p,int fa){
26     if(!p)return;
27     if(!v[p].empty())
28         link(v[p][0],fa),link(v[p][0]+(n<<1),oppo(v[p][0])),
29         link(v[p][0],v[p][0]+(n<<2)),link(fa?fa+(n<<1):300001,oppo(v[p][0])),
30         link(v[p][0]+(n<<1),fa),link((fa?fa+(n<<1):3000001),v[p][0]+(n<<2));
31     for(int i=1;i<v[p].size();++i)
32         link(v[p][i],v[p][i-1]+(n<<1)),link(v[p][i]+(n<<1),oppo(v[p][i])),
33         link(v[p][i],v[p][i]+(n<<2)),link(v[p][i-1]+(n<<2),oppo(v[p][i])),
34         link(v[p][i]+(n<<1),v[p][i-1]+(n<<1)),link(v[p][i-1]+(n<<2),v[p][i]+(n<<2));
35     if(!v[p].empty())dfs(trie[p][0],v[p][v[p].size()-1]+(n<<1)),dfs(trie[p][1],v[p][v[p].size()-1]+(n<<1));
36     else dfs(trie[p][0],fa),dfs(trie[p][1],fa);
37 }
38 int main(){
39     scanf("%d",&n);
40     for(int i=1;i<=n;++i){
41         scanf("%s",s);int l=strlen(s),p=500001;
42         for(int j=0;j<l;++j)if(s[j]=='?'){p=j;break;}
43         s[p]='0';build(rt,0,l,(i<<1)-1);
44         s[p]='1';build(rt,0,l,i<<1);
45         if(p==500001)link((i<<1)-1,i<<1);
46     }
47     dfs(rt,0);
48     for(int i=1;i<=cnt+ex;++i)if(!dfn[i])tarjan(i,0);
49     for(int i=1;i<=n;++i)if(bel[i<<1]==bel[(i<<1)-1]){puts("NO");return 0;}
50     puts("YES");
51 }
View Code

T3~5:咕。。。

在$skyh$大神清楚明白的讲完大神题之后,大概发现自己对$2-sat$的理解一点也不深刻。

加诸板子也差不多快忘掉了,所以有空回来刷刷题。

T4:游戏

Description

$n,m le 10^5,d le 8$

首先除了$x$图之外,每个图都只有两种选择。

然而事实上$d$并不大。

所以我们可以直接枚举每种$x$图用了哪种车,其余部分$2-sat$即可做到$O(m3^d)$

比较机智的一点是,我们发现我们可以处理两种状态的情况,而$3 le 2+2$。

多项式做法肯定想不到所以我们考虑优化非多项式部分。

你有两个选择的额时候,你并不用$2^n$而是用$2-sat$将它改成了多项式。

发现你只要把每个$x$图都当作$a,b$图各做一遍取其中有解的一种即可覆盖$3^d$种选择。而时间复杂度就是$O(n2^d)$的了。

 1 #include<cstdio>
 2 #define S 555555
 3 int n,d,m,R[S],dp[9],a[S],la[S],b[S],lb[S],fir[S],l[S],to[S],ec,dfn[S],low[S],tim,sta[S],ins[S],top,scc,bl[S];
 4 void link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;}
 5 int min(int a,int b){return a<b?a:b;}
 6 void tarjan(int p){
 7     dfn[p]=low[p]=++tim; sta[++top]=p; ins[p]=1;
 8     for(int i=fir[p];i;i=l[i])if(!dfn[to[i]])tarjan(to[i]),low[p]=min(low[p],low[to[i]]);
 9         else if(ins[to[i]])low[p]=min(low[p],dfn[to[i]]);
10     if(low[p]==dfn[p]){
11         scc++;
12         while(ins[p])bl[sta[top]]=scc,ins[sta[top--]]=0;
13     }
14 }
15 int ord(int p,int k){return p<<1|k-(k>R[p]);}
16 bool getans(){
17     for(int i=2;i<=n*2+1;++i)fir[i]=dfn[i]=0; ec=scc=0;
18     for(int i=1;i<=m;++i)
19         if(R[a[i]]==la[i])continue;
20         else if(R[b[i]]==lb[i])link(ord(a[i],la[i]),ord(a[i],la[i])^1);
21         else link(ord(a[i],la[i]),ord(b[i],lb[i])),link(ord(b[i],lb[i])^1,ord(a[i],la[i])^1);
22     for(int i=2;i<=n*2+1;++i)if(!dfn[i])tarjan(i);
23     for(int i=2;i<=n<<1;++i)if(bl[i]==bl[i^1])return 0;
24     for(int i=2,x;i<=n<<1;i+=2)x=bl[i]>bl[i^1],putchar('A'+x+(x>=R[i>>1]));
25     return 1;
26 }
27 int getc(){char ch=getchar();while(ch<'a'||ch>'x')ch=getchar();return ch-'a';}
28 int getC(){char ch=getchar();while(ch<'A'||ch>'X')ch=getchar();return ch-'A';}
29 int main(){
30     scanf("%d%*d",&n);
31     for(int i=1;i<=n;++i)R[i]=getc();
32     for(int i=1;i<=n;++i)if(R[i]>2)dp[++d]=i;
33     scanf("%d",&m);
34     for(int i=1;i<=m;++i)scanf("%d",&a[i]),la[i]=getC(),scanf("%d",&b[i]),lb[i]=getC();
35     for(int i=0;i<1<<d;++i){
36         for(int j=1;j<=d;++j)R[dp[j]]=i>>j-1&1;
37         if(getans())return 0;
38     }puts("-1");
39 }
View Code
原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/11736814.html