一看要求一个最小的最大间隔,最小最大这类字眼,果断的就是二分枚举答案,然后进行验证,验证的时候就有一个炫酷的解法了,就是2-SAT判断是否矛盾,对于一个2-SAT问题,我们可以这么做,先用tarjan,然后判断同一个状态拆成的两个节点是否在一个连通分量内,如果在,表示矛盾,如果不在,表示成立,求可行解的方法就是重构一个有向无环图,按照拓扑序反向输出可行解。
或者说我们可以假设某一个点为真,然后进行染色,如果在染色过程中出现了该点的对立也被染色了,那么就表明此点为真是错误的,那么我们再换成此点为假重新染色,如果无法成功染色就证明该问题无解,如果染上了,那么被着色节点就是可行解。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <cstdlib>
using namespace std;
const int maxn = 44444;
struct TwoSAT{
int n, S[maxn], c;
vector<int> G[maxn];
bool mark[maxn];
bool dfs(int x){
if (mark[x ^ 1]) return 0;
if (mark[x]) return 1;
mark[x] = 1;
S[c++] = x;
for (int i = 0; i < G[x].size(); i++)
if (!dfs(G[x][i])) return 0;
return 1;
}
void init(int n){
this -> n = n;
for (int i = 0; i <= n * 2; i++)
G[i].clear();
memset(mark, 0, sizeof(mark));
}
void add_clause(int x, int xval, int y, int yval){
x = x * 2 + xval;
y = y * 2 + yval;
G[x ^ 1].push_back(y);
G[y ^ 1].push_back(x);
}
bool solve(){
for (int i = 0; i < n * 2; i += 2)
if (!mark[i] && !mark[i + 1]){
c = 0;
if (!dfs(i)){
while(c > 0) mark[S[--c]] = 0;
if (!dfs(i + 1)) return 0;
}
}
return 1;
}
};
int n, T[maxn][2];
TwoSAT solver;
bool check(int t){
solver.init(n);
for (int i = 0; i < n; i++) for (int a = 0; a < 2; a++)
for (int j = i + 1; j < n; j++) for (int b = 0; b < 2; b++)
if (abs(T[i][a] - T[j][b]) < t) solver.add_clause(i, a ^ 1, j, b ^ 1);
if (solver.solve()) return 1;
else return 0;
}
int main(){
while(~scanf("%d", &n) && n){
int l = 0, r = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j < 2; j++){
scanf("%d", &T[i][j]);
r = max(r, T[i][j]);
}
while (l < r){
int m = l + (r - l + 1) / 2;
if (check(m)) l = m;
else r = m - 1;
}
printf("%d\n", l);
}
return 0;
}
上面为刘汝佳解法,下面为tarjan一般解法
参考资料:伍昱《由对称性解2-SAT问题》,赵爽《2-SAT 解法浅析》,刘汝佳《算法竞赛入门经典训练指南》,但是佳哥的解法和本菜所说的不一样。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
#include <vector>
#include <stack>
using namespace std;
const int N = 4444;
int pre[N], sccno[N], lowlink[N], dfs_clock, scc_cnt, T[N][2], n;
stack<int> S;
vector<int> G[N];
void add_edge(int x, int xval, int y, int yval){
x = x * 2 + xval;
y = y * 2 + yval;
G[x ^ 1].push_back(y);
G[y ^ 1].push_back(x);
}
void dfs(int u){
pre[u] = lowlink[u] = ++dfs_clock;
S.push(u);
for (int i = 0; i < G[u].size(); i++){
int v = G[u][i];
if (!pre[v]){
dfs(v);
lowlink[u] = min(lowlink[u], lowlink[v]);
}
else if (!sccno[v])
lowlink[u] = min(lowlink[u], pre[v]);
}
if (lowlink[u] == pre[u]){
scc_cnt += 1;
for (;;){
int x = S.top(); S.pop();
sccno[x] = scc_cnt;
if (x == u) break;
}
}
}
void tarjan(int n){
dfs_clock = scc_cnt = 0;
memset(pre, 0, sizeof(pre));
memset(sccno, 0, sizeof(sccno));
memset(lowlink, 0, sizeof(lowlink));
for (int i = 0; i < n; i++)
if (!pre[i]) dfs(i);
}
bool check(int t){
for (int i = 0; i <= 2 * n; i++)
G[i].clear();
for (int i = 0; i < n; i++) for (int a = 0; a < 2; a++)
for (int j = i + 1; j < n; j++) for (int b = 0; b < 2; b++)
if (abs(T[i][a] - T[j][b]) < t) add_edge(i, a ^ 1, j, b ^ 1);
tarjan(n * 2);
//for (int i = 0; i < n * 2; i++) printf("%d ", sccno[i]);
//printf("\n");
for (int i = 0; i < n * 2; i += 2)
if (sccno[i] == sccno[i + 1]) return 0;
return 1;
}
int main(){
while(~scanf("%d", &n) && n){
int l = 0, r = 0;
for (int i = 0; i < n; i++)
for (int j = 0; j < 2; j++){
scanf("%d", &T[i][j]);
r = max(r, T[i][j]);
}
while (l < r){
int m = l + (r - l + 1) / 2;
if (check(m)) l = m;
else r = m - 1;
}
printf("%d\n", l);
}
return 0;
}