二分图浅学

前言:由于 NOI 大纲中对二分图的要求仅停留在判定,所以本文主要讲解二分图染色。

二分图指:一张图可以分成两个集合,使得两个集合内部没有边相连,边在两个集合之间。

判定二分图的充要条件是:不存在奇环。那么我们可以对于整张图交替染色,如果发现矛盾,存在奇环;否则说明不存在奇环。

其实奇环可以用并查集的扩展域去写,但是二分图染色的题目一般要求求出具体方案,这个用并查集就很难实现(路径压缩后,原来的结构改变)。

根据二分图的概念,集合内没有边相连,所以一般一条边相连的两点,这两点的信息是不同的;如果还要求两点信息相同,那么连一条边权为 \(0\) 的边即可。

下面看几道例题体会二分图染色建模的应用:

[POI2005] DWU-Double-row

题意简述:\(2n\) 个数站成两排(每个数在 \(2n\) 个数中最多出现两遍),一次操作可以交换任意一列中两个数,求使每行数不重复的最少操作数。

分析本题的限制条件:

  • 每行不能出现重复的数字;
  • 一次操作只能交换一列中的数字。

由于是 \(2\) 排,又有一些限制条件,所以想到二分图。

既然每行不能出现重复数字,那么显然要在相同的数字之间连边,这样在染色后,相同数字不可能在一个集合。

并且在相同列的数字之间连边,这样也能保证不会有非法的交换出现。比如下图(红色圈圈起来的是同一排,线连起来的是同一列):

不可能出现非法交换:将 \(b,c\) 交换。

但是有可能出现非法交换:\(d,b\) 交换,\(a,c\) 交换,但是由于题目要求每行不出现重复数字,所以这种叫喊是没有意义的,自然不会算进答案里面。

统计答案,一个环连接的一堆点,可能会出现第一个放左,放右答案不同的情况,这时取最小值即可,因为一个环不会对后面的计算造成影响。

点击查看代码
#include<bits/stdc++.h>
using namespace std;

const int N=1e5+10;
int n,sum1,sum2;
int a[N],f[N],h[N],col[N];
vector<int> g[N];

void add(int x,int y) {
    g[x].push_back(y);
}

void dfs(int nd,int c) {
    col[nd]=c;
    if(col[nd]!=f[nd]) sum1++;
    else sum2++;
    for(auto x:g[nd]) {
        if(col[x]==-1) {
            dfs(x,c^1);
        }
    }
}

int main() {
    cin>>n;
    for(int i=1;i<=n;i++) {
        cin>>a[i];
        if(h[a[i]]) {
            add(h[a[i]],i);
            add(i,h[a[i]]);            
        }
        h[a[i]]=i; 
        f[i]=1;
    }
    for(int i=n+1;i<=2*n;i++) {
        cin>>a[i];
        if(h[a[i]]) {
            add(h[a[i]],i);
            add(i,h[a[i]]);
        }
        h[a[i]]=i;
        f[i]=0;
    }
    //左1右0
    for(int i=1;i<=n;i++) {
        add(i,i+n);
        add(i+n,i);
    }
    memset(col,-1,sizeof(col));
    int ans=0;
    for(int i=1;i<=2*n;i++) {
        sum1=0; sum2=0;
        if(col[i]==-1) {
            dfs(i,0);
            sum1/=2;
            sum2/=2;
            ans+=min(sum1,sum2);
        }
    }
    cout<<ans;
    return 0;
}

Arpa’s overnight party and Mehrdad’s silent entering

吐槽:题目又长又臭。。。没想出来。。。

题意简述:\(n\) 对情侣围一圈坐下,有两种食物,情侣不能吃同一种,连续三个人不能吃一种,问一种可行方案。

情侣不能吃同一种是很好处理的,直接在情侣之间连边即可,但是连续三人不能吃同一种。我们考虑如何拆成两人。

如果规定 \(1,2\) 不能相同,\(3,4\) 不能相同,...,\(2i,2i+1\) 不能相同,能否表示这个限制呢?

显然,满足这个条件,必然可以满足连续三人不能吃同一种,但是,连续三人不同却不一定满足这个条件,比如:

1 1 2 2 1 1 2 2

这样显然是不符合相邻的不同的,但是的确符合连续三个不同。

让我们来考虑,按照上述染色能否构造出答案:

首先判断有没有可能存在奇环,一个人一共两条边,情侣边和组边,如果构成奇环,先考虑 \(3\) 人的情况,那么必然会出现矛盾,所以这种分组方案一定可以整出一种合法方案。

至于上面提到的与连续三个条件不符,也不用考虑了。(反正可以算出答案)

点击查看代码
#include<bits/stdc++.h>
using namespace std;

const int N=2e5+10;
int n;
int a[N],b[N];
vector<int> g[N];

void add(int x,int y) {
    g[x].push_back(y);
}

int col[N];
void dfs(int x,int c) {
    col[x]=c;
    for(int y:g[x]) {
        if(!col[y]) {
            dfs(y,3-c);
        }
    }
}

int main() {
    cin>>n;
    for(int i=1;i<=n;i++) {
        cin>>a[i]>>b[i];
        add(a[i],b[i]);
        add(b[i],a[i]);
    }
    for(int i=1;i<=n;i++) {
        add(2*i,2*i-1);
        add(2*i-1,2*i);
    }
    for(int i=1;i<=2*n;i++) {
        if(!col[i]) dfs(i,1);
    }
    for(int i=1;i<=n;i++) {
        cout<<col[a[i]]<<' '<<col[b[i]]<<endl;
    }

    return 0;
}

[NOIP2008 提高组] 双栈排序

个人感觉难度最高的一道题目。

题目简述:给定两个栈,四种操作:进栈 \(1\),出栈 \(1\) 并输出到序列,进栈 \(2\),出栈 \(2\) 并输出到序列。

先来思考一个问题:如果只有一个栈,那么什么情况下 \(i,j\) 是不能在一个栈内的。

结论:若 \(i<j<k\),且 \(a_k<a_i<a_j\),这时候就不行。

因为 \(a_k\) 要在 \(a_i,a_j\) 之前出栈,且 \(a_i\) 要在 \(a_j\) 之前出栈,但是由于 \(i<j\),所以 \(j\) 不出栈,\(i\) 就不能出栈,这时产生矛盾。

回到本题,既然题目给了两个栈,那么很直观的想法就是将不能在同一个栈内的 \(i,j\) 分配到不同的两个栈内。这时我们可以对不可以的数对进行连边,然后二分图染色,注意尽量使得前面的染 \(1\),后面的染 \(2\)

在输出的时候,需要贪心一下,也很有讲究:

  • 进栈 \(1\)
    • 如果进不去,先尝试将栈 \(1\) 弹出;
    • 如果栈 \(1\) 弹不出,先尝试将栈 \(2\) 弹出;
    • 最后将数字压进栈。
  • 进栈 \(2\)
    • 不管进不进得去,先尝试将栈 \(1\) 弹出;
    • 如果进不去,先将栈 \(1\) 弹出,再将栈 \(2\) 弹出;
    • 最后塞进去。
点击查看代码
#include<bits/stdc++.h>
using namespace std;

const int N=1100;
int n;
int a[N],f[N],col[N];
int g[N][N];

int dfs(int nd,int c) {
    col[nd]=c;
    for(int i=1;i<=n;i++) {
        if(g[nd][i]) {
            if(!col[i]) dfs(i,3-c);
            if(col[i]!=3-c) return 0;
        }
    }
    return 1;
}

int num;
vector<char> ans;
stack<int> s[3];

void Push(int x,int opt) {
    s[opt].push(x);
    if(opt==1) ans.push_back('a');
    else ans.push_back('c');
}

void Pop(int opt) {
    s[opt].pop();
    if(opt==1) ans.push_back('b');
    else ans.push_back('d');
    num++;
}

int main() {
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    f[n+1]=n+1;
    for(int i=n;i>=1;i--) {
        f[i]=min(f[i+1],a[i]);
    }
    for(int i=1;i<=n;i++) {
        for(int j=i+1;j<n;j++) {
            if(a[i]<a[j]&&f[j+1]<a[i]) {
                g[i][j]=g[j][i]=1;
            }
        }
    }
    for(int i=1;i<=n;i++) {
        if(!col[i]&&!dfs(i,1)) {
            cout<<0;
            return 0;
        }
    }

    num=1;
    for(int i=1;i<=n;i++) {
        int id=col[i];
        if(id==2) {
            while(s[1].size()&&s[1].top()==num) Pop(1);
        }
        if(!s[id].size()) Push(a[i],id);
        else if(s[id].top()>a[i]) Push(a[i],id);
        else {
            while(s[id].size()&&s[id].top()==num) Pop(id);
            if(id==2) {
                while(s[1].size()&&s[1].top()==num) Pop(1);
            }            
            if(!s[id].size()) Push(a[i],id);
            else if(s[id].top()>a[i]) Push(a[i],id);
            else {
                while(s[3-id].size()&&s[3-id].top()==num) Pop(3-id);
                while(s[id].size()&&s[id].top()==num) Pop(id);
                if(!s[id].size()) Push(a[i],id);
                else if(s[id].top()>a[i]) Push(a[i],id);
            }
        }
    }
    while(num<=n) {
        while(s[1].size()&&s[1].top()==num) Pop(1);
        while(s[2].size()&&s[2].top()==num) Pop(2);
    }

    for(auto x:ans) cout<<x<<' ';


    return 0;
}

Graph Coloring

吐槽:紫题,但是完全没有难度。

首先一个边是连接在两个点之间的,如果这条边原来是蓝色的,现在要将其变成红色,那么两个点显然只有一个需要变,也就是两个点的状态不同,连一条边即可;同理,若原来是蓝色,变完之后还是蓝色,两点状态相同,不用连边。

最后跑一遍二分图染色计算贡献即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;

const int N=1e5+10;
int n,m;
int cnt0,cnt1;
int w[N],v[N],c[N];
int col[N];
struct node {
    int to,w;
};
vector<node> g[N];
vector<int> ans1,ans2,cod;

void add(int x,int y,int z) {
    g[x].push_back({y,z});
}

int dfs(int nd,int c) {
    col[nd]=c;
    cod.push_back(nd);
    if(c==0) cnt0++;
    else cnt1++;
    for(auto t:g[nd]) {
        int y=t.to,w=t.w;
        if(w==0) {
            if(col[y]==-1&&!dfs(y,c)) return 0;
            if(col[y]!=c) return 0;
        }
        else {
            if(col[y]==-1&&!dfs(y,!c)) return 0;
            if(col[y]!=1-c) return 0;
        }
    }
    return 1;
}

void Print(vector<int> a) {
    cout<<a.size()<<endl;
    for(int x:a) cout<<x<<' ';
}

int main() {
    cin>>n>>m;
    for(int i=1;i<=m;i++) {
        string s; cin>>w[i]>>v[i]>>s;
        if(s=="B") c[i]=0;
        else c[i]=1;
    }
    //color=B
    for(int i=1;i<=m;i++) {
        if(c[i]==0) {
            add(w[i],v[i],0);
            add(v[i],w[i],0);
        }
        else {
            add(w[i],v[i],1);
            add(v[i],w[i],1);
        }
    }
    memset(col,-1,sizeof(col));
    int f1=1;
    for(int i=1;i<=n;i++) {
        if(col[i]==-1) {
            cnt0=0; cnt1=0;
            cod.clear();
            if(!dfs(i,0)) {
                f1=0;
                break;   
            }
            int p=-1;
            if(cnt0<cnt1) p=0;
            else p=1;
            for(int x:cod) {
                if(col[x]==p) ans1.push_back(x);
            }
        }
    }

    //color R
    for(int i=1;i<=n;i++) g[i].clear();
    for(int i=1;i<=m;i++) {
        if(c[i]==1) {
            add(w[i],v[i],0);
            add(v[i],w[i],0);
        }
        else {
            add(w[i],v[i],1);
            add(v[i],w[i],1);
        }
    }
    memset(col,-1,sizeof(col));
    int f2=1;
    for(int i=1;i<=n;i++) {
        if(col[i]!=-1) continue;
        cnt0=cnt1=0;
        cod.clear();
        if(!dfs(i,0)) {
            f2=0;
            break;
        }
        int p=-1;
        if(cnt0<cnt1) p=0;
        else p=1;
        for(int x:cod) {
            if(col[x]==p) ans2.push_back(x);
        }        
    }

    if(!f1&&!f2) cout<<-1;
    else if(f1&&!f2) {
        Print(ans1);
    }
    else if(!f1&&f2) {
        Print(ans2);
    }
    else {
        if(ans1.size()<ans2.size()) Print(ans1);
        else Print(ans2);
    }

    return 0;
}
posted @ 2023-08-17 12:44  2017BeiJiang  阅读(8)  评论(0编辑  收藏  举报