前置技能
定义
K-SAT:给你许多个集合,每个集合都有k个元素。同时又有一些限制(如取x则不能取y),你必须在所有集合中取且仅能取一个元素,求合法方案。
2-SAT:当K取2时的情况。
为什么只考虑K=2的情况呢?
因为当K>2时,已经被证明是一个NPC问题。
算法实现
几个前提
建x->y的边表示选了x就必须选y。在同一集合的元素编号为x,x^1。
建图时,如果x与y有冲突,那么就建x->y^1和y->x^1的边
对称性:如果x能到y,则y^1必然能到x^1。
暴力求解
我们可以对每一个元素进行枚举判断,如果出现了既选x又选x^1的情况,那么就是不合法的。一直枚举到出现解为止。
这种方法可以解决求字典序最小解的情况,时间复杂度为
O(nm)
。
例题及题解:HDU1841
2-SAT
不难发现,当一个点被选择,这个点所在的强连通分量全部要被选择。因此我们可以进行Tarjan缩点。如果发现x和x^1在一个强连通分量内,则说明无解。
现在我们需要证明的就是:当没有上述情况出现时,问题一定有解。
我们把原图(已经缩过点)的反图进行拓扑排序,就可以得到反图中每个点所在强连通分量的拓扑序。
那么如果原图中x所在的强连通分量有指向x^1所在的强连通分量的边,我们就不能选x。在反图中边的方向正好相反。而这在反图拓扑序中的体现就是:x的拓扑序>x^1的拓扑序。而由于Tarjan的缩点编号方法,强连通分量的编号就是反图的拓扑序。因此,我们只需要在x和x^1的所在的强连通分量中取编号小的那一个即可。
上述情况成立仅当2-SAT满足对称性的时候成立。当2-SAT不满足对称性的时候,就会出现x->y,x^1->y^1这种情况。这时当我们取y和x^1时,显然是矛盾的情况。
乱口胡证毕
模板
求任意解:POJ3683
题意:有n场婚礼,每场婚礼时间上下限s和t,持续的时间为lst。所有婚礼必须在s–s+lst或t-lst–t这两个时间段举行。同一时间只能举办一场婚礼。求一种方案使得所有婚礼都举行。
我们把每一场婚礼看成一个集合,两个元素为s–s+lst和t-lst–t。把有冲突的婚礼建边,跑一边2-SAT即可。
代码:
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#define N 2005
#define il inline
using namespace std;
struct edge{ int next,to; }ed[N*N];
int n,m,k,nd,p,h[N],st[N],lt[N];
int tp,num[N],dfn[N],low[N],stk[N];
bool ans[N],f[N];
il int _read(){
int x=0,f=1; char ch=getchar();
while (!isdigit(ch)) { if (ch=='-') f=-1; ch=getchar(); }
while (isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return x*f;
}
il bool jd(int x,int y){ return lt[x]>st[y]&<[y]>st[x]; }
il void addedge(int x,int y){ ed[++k].next=h[x],ed[k].to=y,h[x]=k; }
void tarjan(int x){
dfn[x]=low[x]=++p,stk[++tp]=x,f[x]=true;
for (int i=h[x],v=ed[i].to;i;i=ed[i].next,v=ed[i].to)
if (!dfn[v]) tarjan(v),low[x]=min(low[x],low[v]);
else if (f[v]) low[x]=min(low[x],dfn[v]);
if (low[x]==dfn[x]){
f[x]=false,num[x]=++nd;int y;
do y=stk[tp--],f[y]=false,num[y]=nd; while (y!=x);
}
}
int main(){
n=_read();
for (int i=0;i<(n<<1);i+=2){
int x=_read(),y=_read(),s=x*60+y,lst,t;
x=_read(),y=_read(),t=x*60+y,lst=_read();
if (s+lst>t) return printf("NO\n"),0;
st[i]=s,lt[i]=s+lst,st[i^1]=t-lst,lt[i^1]=t;
}
for (int i=0;i<(n<<1)-2;i+=2) for (int p=0;p<=1;p++)
for (int j=i+2;j<(n<<1);j+=2) for (int q=0;q<=1;q++)
if (jd(i^p,j^q)) addedge(i^p,j^q^1),addedge(j^q,i^p^1);
for (int i=0;i<(n<<1);i++) if (!dfn[i]) tarjan(i);
for (int i=0;i<(n<<1);i+=2)
if (num[i]==num[i^1]) return printf("NO\n"),0;
else if (num[i]<num[i^1]) ans[i]=true; else ans[i^1]=true;
printf("YES\n");
for (int i=0;i<(n<<1);i++)
if (ans[i]) printf("%02d:%02d %02d:%02d\n",st[i]/60,st[i]%60,lt[i]/60,lt[i]%60);
return 0;
}