【洛谷6898】[ICPC2014 WF] Metal Processing Plant(二分图染色+2-SAT)
- 给定\(n\)个点,每两个点之间有一个矛盾值。
- 要求将这些点划分成两个点集,使得两个点集的最大矛盾值之和最小。
- \(n\le200\)
二分图染色
考虑我们从大到小枚举较大的那个矛盾值\(x\),显然矛盾值大于\(x\)的一对点无法放在同一个集合中,我们可以在它们之间连一条边。
这样一来,发现必须要满足当前是一张二分图,而对于图中每一个连通块,它的两部分必须分别放在两个点集中。
由于每次我们只会加入一条边,先判断是否形成了奇环(在同一连通块中且颜色相同),否则如果它们不连通就合并两个连通块(不用显式建图\(dfs\),可以直接暴枚一遍所有点更新所在连通块)。
\(2-SAT\)
我们二分另一个最大矛盾值。
对于矛盾值大于二分值的一对点,它们不能同时被选在这个点集中,也就是它们对应连通块的对应部分不能同时被选在这个点集中。
每个连通块必须要在两种决策中选择一种,又有着这些条件表达式,发现就是一个经典的\(2-SAT\)问题。
注意,我们只需在二分图连通块情况改变时做一遍二分答案+\(2-SAT\),因此只会做\(O(n)\)次。
代码:\(O(n^3logn)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200
using namespace std;
int n,c[N+5],p[N+5],a[N+5][N+5];
struct Data {int x,y,v;I bool operator < (Con Data& o) Con {return v>o.v;}}s[N*N+5];
namespace TwoSAT//2-SAT
{
#define add(x,y) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y)
int ee,lnk[2*N+5];struct edge {int to,nxt;}e[N*N+5];
int d,dfn[2*N+5],low[2*N+5],T,S[2*N+5],IS[2*N+5],ct,bl[2*N+5];
I void Tarjan(CI x)
{
dfn[x]=low[x]=++d,IS[S[++T]=x]=1;for(RI i=lnk[x];i;i=e[i].nxt)
low[x]=min(low[x],dfn[e[i].to]?(IS[e[i].to]?dfn[e[i].to]:n<<1):(Tarjan(e[i].to),low[e[i].to]));
if(dfn[x]==low[x]) {++ct;W(bl[S[T]]=ct,IS[S[T]]=0,S[T--]^x);}
}
I bool Check(CI x)//检验答案
{
RI i,j;for(ee=d=ct=0,i=1;i<=2*n;++i) lnk[i]=dfn[i]=0;//情况
for(i=1;i<=n;++i) for(j=i+1;j<=n;++j) a[i][j]>x&&//矛盾值大于x不能同时选择
(add(p[i]+c[i]*n,p[j]+(c[j]^1)*n),add(p[j]+c[j]*n,p[i]+(c[i]^1)*n));//把条件式表示成图中边
for(i=1;i<=n;++i) i==p[i]&&(!dfn[i]&&(Tarjan(i),0),!dfn[i+n]&&(Tarjan(i+n),0));//Tarjan
for(i=1;i<=n;++i) if(i==p[i]&&bl[i]==bl[i+n]) return 0;return 1;//两种选择在同一强连通分量中说明无解
}
}
int res;I void Calc() {RI l=0,r=1e9,mid;W(l^r) TwoSAT::Check(mid=l+r-1>>1)?r=mid:l=mid+1;res=r;}//二分答案
I bool Link(CI x,CI y)//二分图连边
{
if(p[x]==p[y]) return c[x]^c[y];RI f=p[y],w=c[x]^c[y]^1;
for(RI i=1;i<=n;++i) p[i]==f&&(p[i]=p[x],c[i]^=w);return Calc(),true;//暴枚所有点,若与y同连通块就更新
}
int main()
{
RI i,j,t=0;if(scanf("%d",&n),n==1) return puts("0"),0;
for(i=1;i<=n;++i) for(j=i+1;j<=n;++j) scanf("%d",&a[i][j]),s[++t]=(Data){i,j,a[i][j]};
RI ans=2e9;for(sort(s+1,s+t+1),i=1;i<=n;++i) p[i]=i;Calc();
for(i=1;i<=t;++i) if(ans=min(ans,s[i].v+res),!Link(s[i].x,s[i].y)) break;//从大到小枚举边权
return i>t&&(ans=min(ans,res)),printf("%d\n",ans),0;
}
待到再迷茫时回头望,所有脚印会发出光芒