[專題總結]2-sat及題目&題解(2/5 complete)

啥啥啥2-sat今天就是最後一天了???我纔打兩道題啊。。。ios

%%%yxm永遠領先全世界。。。c++

爲了防止學=沒學因此仍是要記一下,防止忘也確認本身真正理解了吧。ide

2-sat是指2適應性問題,然而知道這個沒有什麼用。學習

k-sat在k>2時都沒有多項式複雜度解法,然而這和你也不要緊。優化

它所解決的問題是,有n個變量,每一個變量都有0/1兩種取值,給出若干限制條件,形如」若x的取值爲0/1,那麼y的取值爲0/1「ui

求是否存在合法方案,而且有時要求構造一組可行解編碼

斷定是否存在有解其實挺簡單的。spa

前置知識:code

  • tarjan求強聯通份量(板子,啊,板子。。。)

咱們對於每個變量的兩種取值各開一個點,每一種限制就是一條有向邊。blog

時刻記住:有向邊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\n",cnt_scc);
32     for(int i=2;i<=n;i+=2)if(val[bel[i]])printf("%d\n",(i^1)-1);else printf("%d\n",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\n",(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:咕。。。

相關文章
相關標籤/搜索