首页 > 其他分享 >二分图浅学

二分图浅学

时间:2023-08-17 12:56:07浏览次数:39  
标签:二分 int 浅学 nd col else id

前言:由于 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;
}

标签:二分,int,浅学,nd,col,else,id
From: https://www.cnblogs.com/zhangyuzhe/p/17632707.html

相关文章

  • 杭电23多校第九场Capoo on tree(二分+树链剖分+可持久化线段树)
    2023HDU多校9__Capooontree(二分+树链剖分+可持久化线段树)题目链接Solution\(Hint1\)考虑如何进行对某一相同点权的所有点进行点权\(+1\)操作,如果我们建立权值线段树,那只需要将权值为\(x\)的点并入权值\(x+1\)即可,但是这样就算我们建立以节点编号为版本的可持久化线段树,也是......
  • D: Space Golf[二分+数学]
    题意大概是给你一个小球,完全弹性碰撞,有若干高度的板子,问从0-target的最小合速度是多少。完全弹性碰撞,意味着给定一个初始速度,运动轨迹将是一个抛物线的不相交的等距(d/(i+1))右移。i是弹跳次数而确定好水平速度后,球的落点就是确定的,那么当y能过的时候,任何大于y的高度也能过去。......
  • 704. 二分查找、27. 移除元素
    704.二分查找、27.移除元素704.二分查找力扣题目链接(opensnewwindow)给定一个n个元素有序的(升序)整型数组nums和一个目标值target,写一个函数搜索nums中的target,如果目标值存在返回下标,否则返回-1。示例1:输入:nums=[-1,0,3,5,9,12],target=9输出:4......
  • 二分图的一些概念
    二分图:将图中的顶点分为两个集合X和Y,X与Y集合没有交集,并且各自集合内的点没有边相连,X集合与Y集合形成边二分匹配:在二分图的基础上,XY两个集合所形成的边集中的子集M,M中的任意两条边没有公共的顶点最大匹配:当M中的边数达到二分图的上限时称为最大匹配完美匹配:二分图中的所有顶点......
  • [二分图] 学习笔记
    定义无向图可以分成两个点集,集合内的点不相连通,不允许存在奇环//二分图的判定#include<bits/stdc++.h>usingnamespacestd;constintN=1e3+10,M=2e6+10;struct{ intto,next;}e[M];inttop,h[N],color[N],n,m;voidadd(intx,inty){ e[++top]......
  • HDU 3829 Cat VS Dog 猫和狗(二分图)结题报告
    听学长说这道题很ex,但是思路想到的话还是挺简单的。可能是受上一道题(放置机器人)的启发,也是找互相冲突的点连线。但是并不是完全一样(废话)放置机器人那道题是找到冲突点连线后直接求最大匹配即可。这道题稍微把思路变换一下,求出最大完美匹配数\(n\)后,说明有\(n*2\)个人的喜好......
  • 704. 二分查找
    参考链接:https://programmercarl.com/0704.二分查找.html#思路给定一个n个元素有序的(升序)整型数组nums和一个目标target,写一个函数搜索nums中的target,如果目标值存在,就返回下标,否则返回-1。tips:假设nums所有元素不重复n在[1,10000]之间nums的每个元素都将在[-9999,9999]之......
  • 二分
    二分介绍相信大家小时候都玩过猜数字的游戏,一般来说,大家都是从中间开始猜,比如猜0~100之间的整数,先猜50,小了再猜75,大了再猜35。我们在玩这个游戏时就使用了二分这个思想,在算法竞赛中,我们对具有单调性的问题便可以使用二分,是一种非常好用的算法,但是二分里面的坑也非常多,稍有不慎便......
  • C++STL库 二分查找,以及对set集合进行二分查找,来源于”leetcode7022. 限制条件下元素之
    C++的头文件<algorithm>中有用于二分查找的函数,lower_bound()、upper_bound()以及binary_search():lower_bound():返回大于等于目标值的第一个位置upper_bound():返回大于目标值的第一个位置,binary_search():若目标值存在则返回true,否则返回false参数列表:(起始位置,结束位置,目标值) ......
  • 二分板子
    1.求最大值最小while(l<=r){  mid=(l+r)>>1;  if(check(mid))ans=mid,r=mid-1;    elsel=mid+1; }例题洛谷p3853路标设置code#include<bits/stdc++.h>usingnamespacestd;intl,n,k,a[100010],r,ll,mid,ans,cnt;boolcheck......