常规操作
模型一: (A,B) ( A , B ) 不能同时取.
对于 A A ,若被“选择”,则只能选 B′ B ′ ,即建边 A−>B′ A − > B ′ ;
对于 B B ,若被“选择”,则只能选 A′ A ′ ,即建边 B−>A′ B − > A ′ ;
模型二: (A,B) ( A , B ) 不能同时不取.
对于 A′ A ′ ,若被“选择”,则只能选 B B ,即建边 A′−>B A ′ − > B ;
对于 B′ B ′ ,若被“选择”,则只能选 A A ,即建边 B′−>A B ′ − > A ;
模型三: (A,B) ( A , B ) 要么都取,要么都不取.
对于 A A ,若被“选择”,则只能选 B B ,即建边 A−>B A − > B ;
对于 B B ,若被“选择”,则只能选 A A ,即建边 B−>A B − > A ;
模型四: (A,A′) ( A , A ′ ) 必取A.
对于 A′ A ′ ,若被“选择”,则只能选 A A ,即建边 A′−>A A ′ − > A ;
概念&原理: 类似二分图的方式,一一去匹配,但这里要通过栈进行记录,在匹配失败后便于还原.故复杂度为 Θ(n∗m) Θ ( n ∗ m ) .
Code(模板):
int qwq,head[N];
struct edge{
int to,next;
}E[N<<1];
void addedge(int x,int y){E[qwq]=(edge){y,head[x]};head[x]=qwq++};
int stk[N],top;
bool vis[N];
bool dfs(int x){
if(vis[x^1])return 0;//对立面已被选,匹配失败
if(vis[x])return 1;//已匹配过
vis[x]=1;
stk[top++]=x;
for(int i=head[x];~i;i=E[i].next) if(!dfs(E[i].to)) return 0;//匹配是败
return 1;//匹配成功
}
bool check(){
mcl(vis,0);
SREP(i,0,n){
if(vis[i<<1] || vis[i<<1|1])continue;
top=0;
if(!dfs(i<<1)){//当前这一面匹配失败
while(top)vis[stk[--top]]=0;//还原
if(!dfs(i<<1|1))return 0;//该面的对立面也匹配失败,故无解
}
}
return 1;
}
void Clear(){
qwq=0;
mcl(head,-1);
}
int main(){
scanf("%d%d",&n,&m);
Clear();
REP(i,1,m){//条件
int a,b;
scanf("%d%d",&a,&b);
//对立面
int x1=a<<1,x0=a<<1|1;
int y1=b<<1,y0=b<<1|1;
/*
根据题意建边
addedge(x1,y0);
addedge(y1,x0);
*/
}
if(!check())puts("-1");//无解
else SREP(i,0,n) printf("%d\n",vis[i<<1]?1:0);//vis即为方案(解)
return 0;
}
概念&原理: 通过tarjan进行缩点,就是满足条件的点为集合,在通过建反图来跑拓扑求方案(解).
解释:
Code(模板):
int qwq,head[N];
struct edge{
int to,next;
}E[N<<1];
void addedge(int x,int y){E[qwq]=(edge){y,head[x]};head[x]=qwq++};
int dfn[N],low[N],tim;
int stk[N],top;
int Id[N],tot;
bool vis[N];
vector<int>G[N];
int In[N],mark[N],rev[N];
queue<int>Q;
void tarjan(int x){
dfn[x]=low[x]=++tim;
stk[++top]=x;
vis[x]=1;
for(int i=head[x];~i;i=E[i].next){
int y=E[i].to;
if(!dfn[y]){
tarjan(y);
chkmi(low[x],low[y]);
}
else if(vis[y]) chmin(low[x],dfn[y]);
}
if(dfn[x]==low[x]){//缩点
tot++;
do{
Id[stk[top]]=tot;
vis[stk[top]]=0;
}while(x!=stk[top--]);
}
}
void Build(){
SREP(x,0,n<<1){
for(int i=head[x];~i;i=E[i].next){
int y=E[i].to;
if(Id[x]!=Id[y])G[Id[y]].pb(Id[x]),In[Id[x]]++;//建反边
}
}
SREP(i,0,n) rev[Id[i<<1]]=Id[i<<1|1],rev[Id[i<<1|1]]=Id[i<<1];//记录对立面
}
void Tuopu(){
while(!Q.empty())Q.pop();
REP(i,1,tot) if(!In[i]) Q.push(i);
while(!Q.empty()){
int x=Q.front();Q.pop();
if(!mark[x])mark[x]=1,mark[rev[x]]=2;//解
SREP(i,0,G[x].size()){
int y=G[x][i];
In[y]--;
if(!In[y])Q.push(y);
}
}
}
void Clear(){
qwq=0;
mcl(head,-1);
tot=top=tim=0;
mcl(dfn,0);
SREP(i,0,n<<1)G[i].clear();
mcl(In,0);
mcl(mark,0);
}
int main(){
scanf("%d%d",&n,&m);
Clear();
REP(i,1,m){//条件
int a,b;
scanf("%d%d",&a,&b);
//对立面
int x1=a<<1,x0=a<<1|1;
int y1=b<<1,y0=b<<1|1;
/*
根据题意建边
addedge(x1,y0);
addedge(y1,x0);
*/
}
SREP(i,0,n<<1) if(!dfn[i]) tarjan(i);
bool f=1;
SREP(i,0,n) if(Id[i<<1]==Id[i<<1|1]){f=0;break;}
if(!f)puts("-1");//无解
else {
Build();//建反图
Tuopu();//跑拓扑
SREP(i,0,n) printf("%d\n",mark[Id[i<<1]]==1?0:1);
}
return 0;
}