2-SAT
前天打了一场codeforces,发现c题是一道2-sat题,然而我并不会打,于是就爆炸.于是现在就仔细学一发2-sat.
首先把每个变量拆成2个点,一个表示它为0,一个表示它为1.把所有限制条件拆成"如果i的值是a,j的值必须是b"这样的条件,然后从表示i的值为a的点向表示j的值为b的点连一条边,再从表示j的值为!b的点向表示i的值为!a的点连一条边.
然后跑tarjan缩点.跑完之后如果对于某个变量,表示它为0的点和表示它为1的点在同一个连通分量了就无解,否则肯定有解.
然后接下来网上的一些写法需要对缩过点的图进行拓扑排序,然而我从zrf大佬的博客得知tarjan出来的顺序就是反过来的拓扑序,于是就可以省去这个步骤,用一些更高妙的方法求.
对于每个变量,如果它为0的点所在的连通分量的编号大于它为1的点所在的连通分量的编号,该变量的值就为1,否则就为0.
用这个方法打了一遍codeforces875C,比我比赛时从网上乱搞来的2-sat板子短多了.
875C的代码在下面.
#include<cstdio> #include<vector> #include<stack> using namespace std; const int maxn=200000; vector<int> a[maxn+10]; int n,m; vector<int> G[maxn+10]; bool value[maxn+10]; int dfn[maxn+10],low[maxn+10],scc[maxn+10],scc_cnt,dfs_cnt; stack<int> S; int ans; void dfs(int x){ dfn[x]=low[x]=++dfs_cnt; S.push(x); for(int i=0;i<G[x].size();++i){ int e=G[x][i]; if(!dfn[e]){ dfs(e); low[x]=min(low[x],low[e]); }else if(!scc[e]) low[x]=min(low[x],dfn[e]); } if(low[x]==dfn[x]){ ++scc_cnt; for(;;){ int t=S.top(); S.pop(); scc[t]=scc_cnt; if(x==t) break; } } } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;++i){ int l; scanf("%d",&l); a[i].resize(l); for(int j=0;j<l;++j) scanf("%d",&a[i][j]); } for(int i=1;i<n;++i){ for(int j=0;j<a[i].size();++j) if(j>=a[i+1].size()){ printf("No\n"); return 0; }else if(a[i][j]!=a[i+1][j]){ int l=a[i][j],r=a[i+1][j]; if(l<r){ G[l<<1].push_back(r<<1); G[(r<<1)-1].push_back((l<<1)-1); }else{ G[l<<1].push_back((l<<1)-1); G[(r<<1)-1].push_back(r<<1); } break; } } for(int i=1;i<=m<<1;++i) if(!dfn[i]) dfs(i); for(int i=1;i<=m;++i) if(scc[i<<1]==scc[(i<<1)-1]){ printf("No\n"); return 0; }else ans+=(value[i]=(scc[i<<1]>scc[(i<<1)-1])); printf("Yes\n%d\n",ans); for(int i=1;i<=m;++i) if(value[i]) printf("%d ",i); return 0; }