啥啥啥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
咱们对于每个变量的两种取值各开一个点,每一种限制就是一条有向边。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 }
T3~5:咕。。。