2-sat
2-sat问题
方案可行性O(M)算法:
建好图之后 利用tarjan强连通算法,最后判断每个点的1和0是否在一个强连通块里。
#include<iostream> #include<cstdio> #include<cstring> using namespace std; struct{ int to,next; }edge[30000100]; //边结点数组 int head[10005],stack[10005],dfn[10005],low[10005],belong[10005]; // head[]头结点数组,stack[]为栈,dfn[]为深搜次序数组,belong[]为每个结点所对应的强连通分量标号数组 // low[u]为u结点或者u的子树结点所能追溯到的最早栈中结点的次序号 int instack[10005],cnt,scnt,top,n,tot; // instack[]为是否在栈中的标记数组 void Add(int x,int y){ //构建邻接表 edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot++; } void Tarjan(int v) //Tarjan算法求有向图的强连通分量 { int min,i,t,j; dfn[v]=low[v]=++cnt; //cnt为次序计数器 instack[v]=1; //标记在栈中 stack[top++]=v; //入栈 for(i=head[v];i!=-1;i=edge[i].next){ //枚举v的每一条边 j=edge[i].to; //v所邻接的边 if(!dfn[j]){ //未被访问 Tarjan(j); //继续向下找 if(low[v]>low[j])low[v]=low[j]; // 更新结点v所能到达的最小次数层 }else if(instack[j]&&dfn[j]<low[v]){ //如果j结点在栈内, low[v]=dfn[j]; } } if(dfn[v]==low[v]){ //如果节点v是强连通分量的根 scnt++; //连通分量标号加1 do{ t=stack[--top]; //退栈 instack[t]=0; //标记不在栈中 belong[t]=scnt; //出栈结点t属于cnt标号的强连通分量 }while(t!=v); //直到将v从栈中退出 } } bool Slove(){ int i; scnt=cnt=top=0; //初始化连通分量标号,次序计数器,栈顶指针为0 memset(dfn,0,sizeof(dfn)); //结点搜索的次序编号数组为0,同时可以当是否访问的数组使用 for(i=0;i<2*n;i++) //枚举每个结点,搜索连通分量 if(!dfn[i]) //未被访问 Tarjan(i); //则找i结点的连通分量 for(int i=0;i<n;i++) if(belong[2*i]==belong[2*i+1])//相等表示两个数据在同一个集合之中 return false; return true; } int main(){ int m,i,x,y,c1,c2,a,b,c,d; while(~scanf("%d",&n)) { memset(head,-1,sizeof(head)); //邻接表的头结点数组初始化为-1 memset(stack,0,sizeof(instack)); memset(belong,-1,sizeof(belong)); memset(low,0,sizeof(low)); scanf("%d",&m); tot=0; while(m--){ scanf("%d%d%d%d",&a,&b,&c,&d); Add((a<<1)+c,(b<<1)+1-d); Add((b<<1)+d,(a<<1)+1-c); /*Add(2*a+c,2*b+(1-d)); Add(2*b+d,2*a+(1-c));*/ } //求强连通分量 if(Slove()) //只有一个强连通分量,说明此图各个结点都可达 printf("YES\n"); else printf("NO\n"); } return 0; }
hdu 1824
题意多个group,每个group有个队长,两个队员。一个group至少要队长留下或者两个队员同时留下。然后给边表示这两个队员不能同时留下。
(开始一直想怎么让两个队员并到一起呢?看了别人的代码,大悟2-sat的“精髓”,每个点有两个状态,每个队员当一个点来处理,so great。)
#include<iostream> #include<cstdio> #include<cstring> using namespace std; struct{ int to,next; }edge[30000100]; //边结点数组 int head[10005],stack[10005],dfn[10005],low[10005],belong[10005]; // head[]头结点数组,stack[]为栈,dfn[]为深搜次序数组,belong[]为每个结点所对应的强连通分量标号数组 // low[u]为u结点或者u的子树结点所能追溯到的最早栈中结点的次序号 int instack[10005],cnt,scnt,top,n,tot; // instack[]为是否在栈中的标记数组 void Add(int x,int y){ //构建邻接表 edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot++; } void Tarjan(int v) //Tarjan算法求有向图的强连通分量 { int min,i,t,j; dfn[v]=low[v]=++cnt; //cnt为次序计数器 instack[v]=1; //标记在栈中 stack[top++]=v; //入栈 for(i=head[v];i!=-1;i=edge[i].next){ //枚举v的每一条边 j=edge[i].to; //v所邻接的边 if(!dfn[j]){ //未被访问 Tarjan(j); //继续向下找 if(low[v]>low[j])low[v]=low[j]; // 更新结点v所能到达的最小次数层 }else if(instack[j]&&dfn[j]<low[v]){ //如果j结点在栈内, low[v]=dfn[j]; } } if(dfn[v]==low[v]){ //如果节点v是强连通分量的根 scnt++; //连通分量标号加1 do{ t=stack[--top]; //退栈 instack[t]=0; //标记不在栈中 belong[t]=scnt; //出栈结点t属于cnt标号的强连通分量 }while(t!=v); //直到将v从栈中退出 } } bool Slove(){ int i; scnt=cnt=top=0; //初始化连通分量标号,次序计数器,栈顶指针为0 memset(dfn,0,sizeof(dfn)); //结点搜索的次序编号数组为0,同时可以当是否访问的数组使用 for(i=0;i<2*n;i++) //枚举每个结点,搜索连通分量 if(!dfn[i]) //未被访问 Tarjan(i); //则找i结点的连通分量 for(int i=0;i<n;i++) if(belong[2*i]==belong[2*i+1])//相等表示两个数据在同一个集合之中 return false; return true; } int z[100001]; int main(){ int m,i,x,y,c1,c2,a,b,c,d; while(~scanf("%d%d",&n,&m)) { memset(head,-1,sizeof(head)); //邻接表的头结点数组初始化为-1 memset(stack,0,sizeof(instack)); memset(belong,-1,sizeof(belong)); memset(low,0,sizeof(low)); tot=0; int id=0; for(int i=0;i<n;i++){ scanf("%d%d%d",&a,&b,&c); Add((a<<1)+1,(b<<1)); Add((a<<1)+1,(c<<1)); Add((b<<1)+1,(a<<1)); Add((c<<1)+1,(a<<1)); } int flag=0; while(m--){ scanf("%d%d",&a,&b); Add((a<<1),(b<<1)+1); Add((b<<1),(a<<1)+1); /*Add(2*a+c,2*b+(1-d)); Add(2*b+d,2*a+(1-c));*/ } if(flag){printf("NO\n");continue;} //求强连通分量 if(Slove()) //只有一个强连通分量,说明此图各个结点都可达 printf("yes\n"); else printf("no\n"); } return 0; }
hdu 3622
题意给了n对点,没对任选一个点做炸弹的圆心 每个炸弹之间不能有重叠 求炸弹最大最大半径。
二分+2-sat
在于建图,对于一个radio 判断每个点之间距离,小于2*radio这说明这两个是不能同时存在的。 构好图对于该2-sat的可行性判断。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 #define eps 1e-6 6 using namespace std; 7 8 9 struct{ 10 int to,next; 11 }edge[30000100]; //边结点数组 12 13 int head[10005],stack[10005],dfn[10005],low[10005],belong[10005]; 14 // head[]头结点数组,stack[]为栈,dfn[]为深搜次序数组,belong[]为每个结点所对应的强连通分量标号数组 15 // low[u]为u结点或者u的子树结点所能追溯到的最早栈中结点的次序号 16 int instack[10005],cnt,scnt,top,n,tot; 17 // instack[]为是否在栈中的标记数组 18 19 void Add(int x,int y){ //构建邻接表 20 edge[tot].to=y; 21 edge[tot].next=head[x]; 22 head[x]=tot++; 23 } 24 25 void Tarjan(int v) //Tarjan算法求有向图的强连通分量 26 { 27 int min,i,t,j; 28 dfn[v]=low[v]=++cnt; //cnt为次序计数器 29 instack[v]=1; //标记在栈中 30 stack[top++]=v; //入栈 31 for(i=head[v];i!=-1;i=edge[i].next){ //枚举v的每一条边 32 j=edge[i].to; //v所邻接的边 33 if(!dfn[j]){ //未被访问 34 Tarjan(j); //继续向下找 35 if(low[v]>low[j])low[v]=low[j]; // 更新结点v所能到达的最小次数层 36 }else if(instack[j]&&dfn[j]<low[v]){ //如果j结点在栈内, 37 low[v]=dfn[j]; 38 } 39 } 40 if(dfn[v]==low[v]){ //如果节点v是强连通分量的根 41 scnt++; //连通分量标号加1 42 do{ 43 t=stack[--top]; //退栈 44 instack[t]=0; //标记不在栈中 45 belong[t]=scnt; //出栈结点t属于cnt标号的强连通分量 46 }while(t!=v); //直到将v从栈中退出 47 } 48 } 49 50 bool Slove(){ 51 int i; 52 scnt=cnt=top=0; //初始化连通分量标号,次序计数器,栈顶指针为0 53 memset(dfn,0,sizeof(dfn)); //结点搜索的次序编号数组为0,同时可以当是否访问的数组使用 54 for(i=0;i<2*n;i++) //枚举每个结点,搜索连通分量 55 if(!dfn[i]) //未被访问 56 Tarjan(i); //则找i结点的连通分量 57 for(int i=0;i<n;i++) 58 if(belong[2*i]==belong[2*i+1])//相等表示两个数据在同一个集合之中 59 return false; 60 return true; 61 } 62 void init(){ 63 memset(head,-1,sizeof(head)); //邻接表的头结点数组初始化为-1 64 memset(stack,0,sizeof(instack)); 65 memset(belong,-1,sizeof(belong)); 66 memset(low,0,sizeof(low)); 67 tot=0; 68 } 69 struct node{ 70 double x; 71 double y; 72 double len(node &a){ 73 return sqrt((x-a.x)*(x-a.x)+(y-a.y)*(y-a.y)); 74 } 75 void in(){ 76 scanf("%lf%lf",&x,&y); 77 } 78 }P[1000]; 79 int cal(double mid){ 80 init(); 81 // cout<<mid<<" dafsdf"<<endl; 82 for(int i=0;i<(n<<1);i++){ 83 for(int j=i+1;j<(n<<1);j++)if(j!=(i^1)){ 84 if(P[i].len(P[j])<mid){ 85 Add(i,j^1); 86 Add(j,i^1); 87 } 88 } 89 } 90 if(Slove())return 1; 91 return 0; 92 } 93 int main(){ 94 int m,i,x,y,c1,c2,a,b,c,d; 95 while(~scanf("%d",&n)) 96 { 97 for(int i=0;i<n;i++){ 98 P[i<<1].in(); 99 P[(i<<1)+1].in(); 100 } 101 double l=0,r=10001; 102 double mid; 103 while(l<=r){ 104 mid=(l+r)/2.0; 105 if(cal(mid)){ 106 l=mid+eps; 107 } 108 else{ 109 r=mid-eps; 110 } 111 } 112 printf("%.2lf\n",l/2.0); 113 /* 114 while(m--){ 115 scanf("%d%d%d%d",&a,&b,&c,&d); 116 Add((a<<1)+c,(b<<1)+1-d); 117 Add((b<<1)+d,(a<<1)+1-c); 118 Add(2*a+c,2*b+(1-d)); 119 Add(2*b+d,2*a+(1-c)); 120 }*/ 121 //求强连通分量 122 /*if(Slove()) //只有一个强连通分量,说明此图各个结点都可达 123 printf("YES\n"); 124 else 125 printf("NO\n");*/ 126 } 127 return 0; 128 129 }
poj 2723
n对钥匙对应n对锁 一对钥匙中,取了一个钥匙另一个就不能取,m扇门每个门上有两个种锁,打开的条件是至少一把锁被打开,问最多能打开多少门(门是有先后顺序的,前面的打开才能打开后面的)。
二分答案
一对钥匙对应两种状态,一扇门取了一把钥匙x,则对应的x‘则不能取,那么存在x’类型的门必须由另一种钥匙打开。
1 #include <iostream> 2 #include <cstdio> 3 #include <vector> 4 #include <stack> 5 #include <cstring> 6 using namespace std; 7 8 const int N=3000; 9 const int inf=10000010; 10 int dfn[N],low[N],dfs_clock; 11 int belong[N],scc; 12 13 stack<int>sta; 14 int n,m; 15 16 vector<int> g[N]; 17 18 int door[N][2]; 19 int mapping[N]; 20 int dfs(int u,int pre){ 21 dfn[u]=low[u]=++dfs_clock; 22 int size=g[u].size(); 23 sta.push(u); 24 for(int i=0;i<size;i++){ 25 int v=g[u][i]; 26 if(!dfn[v]){ 27 int lowv=dfs(v,u); 28 low[u]=min(low[u],lowv); 29 }else if(belong[v]==-1){ 30 low[u]=min(low[u],dfn[v]); 31 } 32 } 33 if(low[u]==dfn[u]){ 34 int x; 35 do{ 36 x=sta.top();sta.pop(); 37 belong[x]=scc; 38 }while(x!=u); 39 scc++; 40 } 41 return low[u]; 42 } 43 int two_sat(){ 44 45 for(int i=0;i<2*n;i++)if(!dfn[i]){ 46 dfs(i,-1); 47 } 48 for(int i=0;i<n;i++){ 49 if(belong[i*2]==belong[i*2+1])return 0; 50 } 51 return 1; 52 } 53 void init(){ 54 dfs_clock=scc=0; 55 for(int i=0;i<2*(n+1);i++)g[i].clear(); 56 memset(belong,-1,sizeof(int)*(2*n)); 57 memset(dfn,0,sizeof(int)*(2*n)); 58 } 59 int ok(int mid){ 60 init(); 61 for(int i=1;i<=mid;i++){ 62 int x=door[i][0]; 63 for(int j=1;j<=mid;j++){ 64 if(door[j][0]==(x^1)){ 65 g[x].push_back(door[j][1]); 66 } 67 if(door[j][1]==(x^1)){ 68 g[x].push_back(door[j][0]); 69 } 70 } 71 x=door[i][1]; 72 for(int j=1;j<=mid;j++){ 73 if(door[j][0]==(x^1)){ 74 g[x].push_back(door[j][1]); 75 } 76 if(door[j][1]==(x^1)){ 77 g[x].push_back(door[j][0]); 78 } 79 } 80 } 81 return two_sat(); 82 } 83 int main() 84 { 85 int a,b; 86 while(scanf("%d%d",&n,&m),n||m){ 87 for(int i=0;i<n;i++){ 88 scanf("%d%d",&a,&b); 89 mapping[a]=2*i; 90 mapping[b]=2*i+1; 91 } 92 for(int i=1;i<=m;i++){ 93 scanf("%d%d",&a,&b); 94 door[i][0]=mapping[a]; 95 door[i][1]=mapping[b]; 96 } 97 int l=0,r=m;int mid; 98 while(l<=r){ 99 mid=(l+r)>>1; 100 if(ok(mid)){ 101 l=mid+1; 102 } 103 else{ 104 r=mid-1; 105 } 106 } 107 printf("%d\n",l-1); 108 } 109 return 0; 110 }
hdu 1816
大意与上一题相近,区别是本题中n对钥匙里可能有重复出现的(比如1和2,1和3,这时如果选了1,那么2,3都不能选),就不能形成2-sat的要求。直接看做每个钥匙选与不选。
1 #include <iostream> 2 #include <cstdio> 3 #include <vector> 4 #include <stack> 5 #include <cstring> 6 using namespace std; 7 8 const int N=5000; 9 const int inf=10000010; 10 int dfn[N],low[N],dfs_clock; 11 int belong[N],scc; 12 13 stack<int>sta; 14 int n,m; 15 16 vector<int> g[N]; 17 18 int door[N][2]; 19 vector<int>key[N]; 20 int dfs(int u,int pre){ 21 dfn[u]=low[u]=++dfs_clock; 22 int size=g[u].size(); 23 sta.push(u); 24 for(int i=0;i<size;i++){ 25 int v=g[u][i]; 26 if(!dfn[v]){ 27 int lowv=dfs(v,u); 28 low[u]=min(low[u],lowv); 29 }else if(belong[v]==-1){ 30 low[u]=min(low[u],dfn[v]); 31 } 32 } 33 if(low[u]==dfn[u]){ 34 int x; 35 do{ 36 x=sta.top();sta.pop(); 37 belong[x]=scc; 38 }while(x!=u); 39 scc++; 40 } 41 return low[u]; 42 } 43 int two_sat(){ 44 45 for(int i=0;i<4*n;i++)if(!dfn[i]){ 46 dfs(i,-1); 47 } 48 for(int i=0;i<2*n;i++){ 49 if(belong[i*2]==belong[i*2+1])return 0; 50 } 51 return 1; 52 } 53 void init(){ 54 dfs_clock=scc=0; 55 for(int i=0;i<4*(n);i++)g[i].clear(); 56 memset(belong,-1,sizeof(int)*(4*n)); 57 memset(dfn,0,sizeof(int)*(4*n)); 58 } 59 int ok(int mid){ 60 init(); 61 for(int i=0;i<2*n;i++){ 62 int size=key[i].size(); 63 for(int j=0;j<size;j++){ 64 g[i*2].push_back((key[i][j]*2)^1); 65 } 66 } 67 for(int i=1;i<=mid;i++){ 68 int x=door[i][0],y=door[i][1]; 69 g[x*2^1].push_back(y*2); 70 g[y*2^1].push_back(x*2); 71 72 } 73 for(int i=1;i<=mid;i++){ 74 int x=door[i][0]; 75 int size=key[x].size(); 76 for(int k=0;k<size;k++){ 77 int v=key[x][k]; 78 for(int j=1;j<=mid;j++){ 79 if(door[j][0]==(v)){ 80 g[x*2].push_back(door[j][1]*2); 81 } 82 if(door[j][1]==(v)){ 83 g[x*2].push_back(door[j][0]*2); 84 } 85 } 86 } 87 x=door[i][1]; 88 size=key[x].size(); 89 for(int k=0;k<size;k++){ 90 int v=key[x][k]; 91 for(int j=1;j<=mid;j++){ 92 if(door[j][0]==(v)){ 93 g[x*2].push_back(door[j][1]*2); 94 } 95 if(door[j][1]==(v)){ 96 g[x*2].push_back(door[j][0]*2); 97 } 98 } 99 } 100 } 101 return two_sat(); 102 } 103 int main() 104 { 105 int a,b; 106 while(scanf("%d%d",&n,&m),n||m){ 107 for(int i=0;i<2*n;i++)key[i].clear(); 108 for(int i=0;i<n;i++){ 109 scanf("%d%d",&a,&b); 110 key[a].push_back(b); 111 key[b].push_back(a); 112 } 113 for(int i=1;i<=m;i++){ 114 scanf("%d%d",&a,&b); 115 door[i][0]=a; 116 door[i][1]=b; 117 } 118 int l=0,r=m;int mid; 119 while(l<=r){ 120 mid=(l+r)>>1; 121 if(ok(mid)){ 122 l=mid+1; 123 } 124 else{ 125 r=mid-1; 126 } 127 } 128 printf("%d\n",l-1); 129 } 130 return 0; 131 }

浙公网安备 33010602011771号