2-sat 问题 【例题 Flags(2-sat+线段树优化建图)】
序:
模拟赛考了一道 2-sat 问题。之前从来没听过……
考完才发现其实这个东东只要一个小小的 tarjan 求强连通分量就搞定了。
这个方法真是巧妙啊,拿来讲讲。
What is it? [・_・?]
这个算法是为了应对一些这样的条件:x1 与 x1 中至少有一个成立
算法:
既然是“2"-sat,那就要充分发挥 “two” 的优势,由于x1限制关系,有一些点我们选了后,有一些点就不能选了,那我们就只能选另一个点了满足 x2。
我们把 a 想它必须选的点 b 连有向边,这样就构成了一个图,判定的话,判断是否有两个不能同时存在的点在一个强连通分量中就好了!
这有一位大佬写的比我更好 OTZ
当然重点是讲模拟赛的题!
例题 Flags:
题目大意:数轴上有 n 个旗子,第 i 个可以插在坐标 xi 或者 yi,最大化两两旗子之间的最小距离。
题解:
我们可以二分答案,转化为一个 2-sat 问题。
二分最小距离 d,那么一个点 a 向左向右 d 的 b 点都不能选,那么选了 a 就必须选 b 的另一个位置,然后我们连边,最后判断是不是有一个点的两个位置是不是在一个强连通分量里,在则不能,否则可以。
这题还有一个点就是如果直接建边的话,就有 $n^2$ 条边,你会发现这题一个点的连边都是连续的,那就不妨线段树优化建图。具体思想戳这里。
CODE:
1 #include<iostream> 2 #include<cstdio> 3 #include<stack> 4 #include<algorithm> 5 #include<cstring> 6 using namespace std; 7 8 int v[100005]; 9 int n,x,y,tot=0,h[100005]; 10 int scc[100005],dfn[100005],low[100005],C,cnt; 11 bool vis[100005]; 12 struct Edge{ 13 int x,next; 14 }e[5000000]; 15 pair<int,int> a[20005]; 16 stack<int> s; 17 18 inline void add_edge(int x,int y){ 19 e[++tot].x=y; 20 e[tot].next=h[x],h[x]=tot; 21 } 22 23 void tarjan(int x){ 24 dfn[x]=low[x]=++cnt; 25 s.push(x),vis[x]=true; 26 for(int i=h[x];~i;i=e[i].next){ 27 if(!dfn[e[i].x]){ 28 tarjan(e[i].x),low[x]=min(low[x],low[e[i].x]); 29 }else if(vis[e[i].x]){ 30 low[x]=min(low[x],dfn[e[i].x]); 31 } 32 } 33 if(dfn[x]==low[x]){ 34 C++; 35 int tmp; 36 for(;;){ 37 tmp=s.top(); 38 vis[tmp]=false,scc[tmp]=C; 39 s.pop(); 40 if(tmp==x)break; 41 } 42 } 43 } 44 45 void build(int o,int l,int r){ 46 if(r-l==1){ 47 add_edge(o+n*2,a[l].second^1); 48 return; 49 } 50 add_edge(o+n*2,(o<<1)+n*2); 51 add_edge(o+n*2,(o<<1|1)+n*2); 52 int mid=l+r>>1; 53 build(o<<1,l,mid),build(o<<1|1,mid,r); 54 } 55 56 void link(int o,int l,int r,int x,int y,int a){ 57 if(l>=x&&r<=y){ 58 add_edge(a,o+n*2); 59 return; 60 } 61 int mid=l+r>>1; 62 if(x<mid)link(o<<1,l,mid,x,y,a); 63 if(y>mid)link(o<<1|1,mid,r,x,y,a); 64 } 65 66 inline pair<int,int> get(int x,int len){ 67 int l=0,r=x; 68 pair<int,int> ans; 69 while(l<r){ 70 int mid=l+r>>1; 71 if(a[x].first-a[mid].first<len)r=mid; 72 else l=mid+1; 73 } 74 ans.first=l; 75 l=x,r=n*2-1; 76 while(l<r){ 77 int mid=l+r+1>>1; 78 if(a[mid].first-a[x].first<len)l=mid; 79 else r=mid-1; 80 } 81 ans.second=l; 82 return ans; 83 } 84 85 inline bool check(int d){ 86 memset(scc,0,sizeof(scc)); 87 memset(dfn,0,sizeof(dfn)); 88 memset(low,0,sizeof(low)); 89 memset(h,-1,sizeof(h)),tot=0; 90 build(1,0,n*2); 91 for(int i=0;i<n*2;i++){ 92 int id=a[i].second; 93 pair<int,int> x=get(i,d); 94 if(i<x.second)link(1,0,n*2,i+1,x.second+1,id); 95 if(x.first<i)link(1,0,n*2,x.first,i,id); 96 } 97 C=cnt=0; 98 for(int i=0;i<n*2;i++)if(!dfn[i])tarjan(i); 99 for(int i=0;i<n*2;i++) 100 if(scc[a[i].second]==scc[a[i].second^1])return false; 101 return true; 102 } 103 104 int main(){ 105 scanf("%d",&n); 106 for(int i=0;i<n;i++){ 107 scanf("%d%d",&x,&y); 108 a[2*i+1]=make_pair(x,2*i+1); 109 a[2*i]=make_pair(y,2*i); 110 } 111 sort(a,a+n*2); 112 int l=0,r=1000000000; 113 while(l<r){ 114 int mid=l+r+1>>1; 115 if(check(mid))l=mid; 116 else r=mid-1; 117 } 118 printf("%d",l); 119 }