前言:
一开始接触2-sat问题的时候我觉得一切都是那么显然。。。然后碰到题目就上2-sat。。。毫无意外地WA了一堆。
然后我以为是有鬼畜的数据,于是没有调。
然后我做到了这道题。。。写着写着突然对自己以前的理解产生了怀疑,感觉每一步都很需要证明啊。。。
没有证明的话,瞎写当然会WA。于是我就证(luan)明(gao)了一发。
题意:裸题不说了。orz。
这里非常重要的一点在于,ab是互相厌恶的。所以建图具有对称性。
对称性有什么用呢??
首先是一种暴力的方法, 可以输出字典序最小的方案。
#include
using namespace std;
#define rep(x,y,z) for (int x=y; x<=z; x++)
#define downrep(x,y,z) for (int x=y; x>=z; x--)
#define ms(x,y,z) memset(x,y,sizeof(z))
#define LL long long
#define repedge(x,y) for (int x=hed[y]; ~x; x=edge[x].nex)
inline int read(){
int x=0; int w=0; char ch=0;
while (ch<'0' || ch>'9') w|=ch=='-',ch=getchar();
while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return w? -x:x;
}
const int N=16005;
const int M=N*2;
int col[N],hed[N],tmp[N],cnt,n,m,nedge;
struct Edge{ int to,nex; }edge[M<<1];
int oth(int x){ return (x&1)? (x+1):(x-1); }
void addedge(int a,int b){
edge[nedge].to=b; edge[nedge].nex=hed[a]; hed[a]=nedge++;
}
int dfs(int k){
if (col[oth(k)]==1) return 0;
if (col[k]==1) return 1;
tmp[++cnt]=k; col[k]=1;
repedge(i,k)
if (!dfs(edge[i].to)) return 0;
return 1;
}
int work(){
rep(i,1,n){
if ((col[i])||(col[oth(i)])) continue;
cnt=0; if (!dfs(i)){ rep(j,1,cnt) col[tmp[j]]=col[oth(tmp[j])]=0;
if (!dfs(oth(i))) return 0; }
}
return 1;
}
int main(){
n=read(); n<<=1; m=read(); nedge=0; ms(hed,-1,hed);
rep(i,1,m){ int a=read(); int b=read(); addedge(a,oth(b)); addedge(b,oth(a)); }
if (work()) { rep(i,1,n) if (col[i]) printf("%d\n",i); }
else puts("NIE");
return 0;
}
这个做法看起来毫无毛病,其实是要证明的。
对于{i0,i1},
1.只能选i0或只能选i1。那当然直接染就行了啊……
2.看起来两个都可以的样子,染i0。
->Q:有没有可能染了i0以后导致之后存在一个{j0,j1},都不行,而染i1就行?
A:不可能的,如果存在这样的情况则j0j1都和i0互斥,由于对称性,i0到它们两者肯定都有边,i0不是合法点,不可能被染色。
以上是O(nm)的暴力染色法,太暴力辣。。。
如果只要求构造任意一种方案的话,其实有更简单的做法。
强连通分量缩点+拓扑排序。
#include
using namespace std;
#define rep(x,y,z) for (int x=y; x<=z; x++)
#define downrep(x,y,z) for (int x=y; x>=z; x--)
#define ms(x,y,z) memset(x,y,sizeof(z))
#define LL long long
#define repedge(x,y) for (int x=hed[y]; ~x; x=edge[x].nex)
inline int read(){
int x=0; int w=0; char ch=0;
while (ch<'0' || ch>'9') w|=ch=='-',ch=getchar();
while (ch>='0' && ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
return w? -x:x;
}
const int N=16005;
const int M=20005;
int n,m,low[N],dfn[N],hed[N],Nedge,head[N],tot,nedge,Oth[N],bel[N],sz[N],col[N],ind[N],scc;
queue Q;
vector dt[N];
stack s;
map mat[N];
struct Edge{ int to,nex; }edge[M<<1],E[M<<1];
int oth(int x){ return (x&1)? (x+1):(x-1); }
void addedge(int a,int b){
edge[nedge].to=b; edge[nedge].nex=hed[a]; hed[a]=nedge++;
}
void tarjan(int k){
dfn[k]=low[k]=++tot; s.push(k);
repedge(i,k){
int v=edge[i].to;
if (!dfn[v]){ tarjan(v); low[k]=min(low[k],low[v]); }
else if (!bel[v]) low[k]=min(low[k],dfn[v]);
}
if (low[k]==dfn[k]){
++scc; for(;;){ int x=s.top(); s.pop(); ++sz[scc]; bel[x]=scc;
dt[scc].push_back(x); if (x==k) break; }
}
}
#define repE(x,y) for(int x=head[y]; ~x; x=E[x].nex)
void addE(int a,int b){
E[Nedge].to=b; E[Nedge].nex=head[a]; head[a]=Nedge++;
}
void rebuild(int t){
rep(i,0,sz[t]-1){
int k=dt[t][i];
repedge(j,k) if ((bel[edge[j].to]!=t)&&(!mat[bel[edge[j].to]][t]))
{ mat[bel[edge[j].to]][t]=1; addE(bel[edge[j].to],t); ++ind[t]; }
}
}
void tuopu(){
rep(i,1,scc) if (!ind[i]) Q.push(i); ms(col,-1,col);
while (!Q.empty()){
int k=Q.front(); Q.pop();
if (col[k]==-1){ col[k]=1; col[Oth[k]]=0; }
repE(i,k){ int v=E[i].to;
--ind[v]; if (!ind[v]) Q.push(v);
}
}
}
int main(){
n=read(); n<<=1; m=read(); nedge=0; ms(hed,-1,hed);
rep(i,1,m){ int a=read(); int b=read(); addedge(a,oth(b)); addedge(b,oth(a)); }
rep(i,1,n) if (!dfn[i]) tarjan(i);
rep(i,1,n) if (bel[i]==bel[oth(i)]) { puts("NIE"); return 0; }
rep(i,1,n) Oth[bel[i]]=bel[oth(i)];
Nedge=0; ms(head,-1,head); rep(i,1,scc) rebuild(i);
tuopu(); rep(i,1,n) if (col[bel[i]]==1) printf("%d\n",i);
return 0;
}
来研究一下这张图吧。
1.对于任意强连通分量f,它内部选择了一个点则整个f都要选,不选某个点则整个f都不能选。即要么一起选要么一起不选。而所有满足这样性质的点也必然属于同一个强连通分量。
2.对于任意分量f,它内部所有点的反点一定属于同一个强连通分量。这一点由1可以证明。
(如果一起选了这些点,它们的反点一定一起不选;反之亦然,由1得,所有反点属于同一强连通分量。)
于是每个分量有唯一的反分量,证明了分量和点是完全等同的。
3.观察边。边其实是一种依赖关系:
如果选a则一定要选b。这句话其实就是说,选了b可能选a,不选b一定不选a。
即,如果b=0则a=0,如果b=1则a随意。
反着建边,原本a->b,建成b->a,完美地表示了这种依赖关系,可以拓扑排序了。
拓扑时,发现没被染的点就染成合法即可。(不能乱染啊染成不合法要出人命的)
为啥这样是对的呢?
证明:b->c,如果b=0,则c=0;
设f的反分量是f'。
3.1如果f被染成0,一定是因为f'被染成了1。
3.2f和f',一定是先出现的那个被染成1。
3.3根据对称性,如果存在边b->c,一定存在边c'->b'。*重点!!
3.4根据拓扑排序的性质,如果存在边b->c,则time[b]