首页 > 其他分享 >二分图染色

二分图染色

时间:2024-05-04 11:22:35浏览次数:27  
标签:二分 return int 染色 dfs color graph include

二分图

image

bool dfs(int u, int c) {
	if (color[u] == c) 
		return true;
	else if (color[u] == 3 - c) 
		return false;
	color[u] = c;
	for (int v : graph[u])
		if (!dfs(v, 3 - c)) return false;
	return true;
}

image

习题:P1330 封锁阳光大学

解题思路

按照题目要求,每一条边所连接的点中,至少要有一个被选中,但又不能同时选中。因此可以转化为二分图染色问题。答案取两种颜色数量较少的那个。

参考代码
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 10005;
vector<int> graph[N];
int color[N], cnt[3];
bool dfs(int u, int c) {
    if (color[u] == c) return true;
    else if (color[u] == 3 - c) return false;
    color[u] = c; cnt[c]++;
    for (int v : graph[u])
        if (!dfs(v, 3 - c)) return false;
    return true;
}
int main()
{
    int n, m; scanf("%d%d", &n, &m);
    while (m--) {
        int u, v; scanf("%d%d", &u, &v);
        graph[u].push_back(v); graph[v].push_back(u);
    }
    bool ok = true;
    int ans = 0; 
    for (int i = 1; i <= n; i++) 
        if (color[i] == 0) {
            cnt[1] = cnt[2] = 0;
            ok &= dfs(i, 1);
            if (!ok) break;
            ans += min(cnt[1], cnt[2]);
        }
    if (!ok) printf("Impossible\n");
    else printf("%d\n", ans);
    return 0;
}

习题:CF862B Mahmoud and Ehab and the bipartiteness

解题思路

若二分图中两种点的集合的大小分别为 \(|S_1|\) 和 \(|S_2|\),则根据二分图的定义,两种集合间的点都是可以连边的,因此最多 \(|S_1| \times |S_2|\) 条边。所以还可以添加 \(|S_1| \times |S_2| - (n - 1)\) 条边。

参考代码
#include <cstdio>
#include <vector>
using namespace std;
const int N = 100005;
vector<int> graph[N];
int cnt[3], color[N];
void dfs(int u, int c) {
    color[u] = c; cnt[c]++;
    for (int v : graph[u])
        if (color[v] == 0) dfs(v, 3 - c); 
}
int main()
{
    int n; scanf("%d", &n);
    for (int i = 1; i < n; i++) {
        int u, v; scanf("%d%d", &u, &v);
        graph[u].push_back(v); graph[v].push_back(u);
    }
    dfs(1, 1);
    printf("%lld\n", 1ll * cnt[1] * cnt[2] - (n - 1));
    return 0;
}

习题:P6185 [NOI Online #1 提高组] 序列

解题思路

把每个位置看成一个点。

对于 \(2\) 操作,如果两个位置连通则意味着可以使一个位置 \(+1\) 而另一个位置 \(-1\),这样一来对于整个连通块,可以使其在总和不变的情况下任意加减,因此可以用并查集将一个连通块看成一个点。

对于 \(1\) 操作连边,如果形成的图是二分图,则可以保证其两个集合内的总和之差保持不变的情况下任意加减。如果形成的图不是二分图,则可以保证整个连通块总和奇偶性保持不变的情况下任意加减。

参考代码
#include <cstdio>
#include <vector>
#include <cmath>
using namespace std;
typedef long long LL;
const int N = 100005;
int a[N], b[N], color[N], fa[N];
LL sum[3], delta[N];
struct Operation {
    int t, u, v;
};  
Operation op[N];
vector<int> graph[N];
int query(int x) {
    return fa[x] == x ? x : fa[x] = query(fa[x]);
}
void merge(int x, int y) {
    int qx = query(x), qy = query(y);
    if (qx != qy) {
        fa[qx] = qy;
        delta[qy] += delta[qx];
    }
}
bool dfs(int u, int c) {
    if (color[u] == c) return true;
    else if (color[u] == 3 - c) return false;
    color[u] = c; sum[c] += delta[u];
    bool ret = true;
    for (int v : graph[u]) {
        if (!dfs(v, 3 - c)) {
            ret = false; // 注意这里不管是否构成二分图都要把染色流程走完
        }
    }
    return ret;
}
int main()
{
    int t; scanf("%d", &t);
    while (t--) {
        int n, m; scanf("%d%d", &n, &m);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            fa[i] = i; color[i] = 0;
            graph[i].clear();
        }
        for (int i = 1; i <= n; i++) {
            scanf("%d", &b[i]);
            delta[i] = b[i] - a[i];
        }
        for (int i = 1; i <= m; i++) {
            scanf("%d%d%d", &op[i].t, &op[i].u, &op[i].v);
            if (op[i].t == 2) {
                merge(op[i].u, op[i].v);
            }
        }
        for (int i = 1; i <= m; i++) {
            if (op[i].t == 1) {
                int qu = query(op[i].u), qv = query(op[i].v);
                graph[qu].push_back(qv); graph[qv].push_back(qu);
            }
        }
        bool ans = true;
        for (int i = 1; i <= n; i++) {
            if (color[i] == 0 && query(i) == i) {
                sum[1] = sum[2] = 0;
                bool ok = dfs(i, 1);
                if (ok && sum[1] != sum[2]) {
                    ans = false; break;
                }
                // 注意坑点:C++中的负数取余
                if (!ok && (abs(sum[1]) + abs(sum[2])) % 2 == 1) {
                    ans = false; break;
                }
            }
        }
        printf("%s\n", ans ? "YES" : "NO");
    }
    return 0;
}

习题:P1155 [NOIP2008 提高组] 双栈排序

解题思路

首先考虑只有一个栈的情况。用一个栈去模拟可以得到部分分(靠一个栈可以搞定的数据点以及结果为 \(0\) 的数据点)。

像 \(2 3 1\) 这个样例需要两个栈才能完成,实际上可以概括为对于 \(i < j < k\) 三个位置存在 \(a_k < a_i < a_j\),此时 \(a_i\) 和 \(a_j\) 无法共存在同一个栈中。因为 \(a_k\) 需要在 \(a_i\) 与 \(a_j\) 之前出栈,但 \(a_i\) 又需要再 \(a_j\) 之前出栈,产生了矛盾。

因此可以预处理每个数之后最小的数,这样就可以在 \(O(n^2)\) 的时间复杂度下完成每一对 \((i,j)\) 能否共存在一个栈中的判断。

对于不能共存的两个数,实际上就需要尝试交给两个栈分别处理,则问题被转化为二分图染色问题。对于每一对不能共存的 \(i\) 和 \(j\) 连边,如果不存在合法的染色方案,则说明无解。

接下来考虑如何保证字典序最小:

染色时让编号小的数尽量放入第一个栈,最终每个点的染色方案代表其交给哪一个栈来处理。用两个栈模拟操作时,注意如果第二个栈的栈顶是下一个排完序的数时,不一定马上就将其出栈(d 操作),此时有一部分 a 操作可以先引入进来(字典序更小),而这时可行的 a 操作需要保证新入栈的数属于第一个栈并且小于第一个栈的栈顶(否则说明它至少要等当前的栈顶出栈之后才能入栈)。

参考代码
#include <cstdio>
#include <stack>
#include <vector>
#include <queue>
using namespace std;
const int N = 1005;
int n, a[N], color[N], ans[N * 2], suf[N], g[N][N];
bool dfs(int u, int c) {
    if (color[u] == c) 
        return true;
    else if (color[u] == 3 - c) 
        return false;
    color[u] = c;
    for (int i = 1; i <= n; i++) 
        if (g[u][i] && !dfs(i, 3 - c)) return false;
    return true;
}
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    // suf[i]记录a[i]后最小的数
    int minnum = n;
    for (int i = n - 1; i >= 1; i--) {
        suf[i] = minnum;
        if (a[i] < a[minnum]) minnum = i;
    }
    for (int i = 1; i < n; i++)
        for (int j = i + 1; j < n; j++) {
            // 寻找是否存在i<j<k而a[k]<a[i]<a[j]
            if (a[i] < a[j] && a[suf[j]] < a[i]) {
                g[i][j] = g[j][i] = 1;
            }
        }
    bool ok = true;
    for (int i = 1; i <= n; i++)
        if (color[i] == 0) {
            ok = dfs(i, 1);
            if (!ok) break;
        }
    if (!ok) printf("0\n");
    else {
        int cur = 1, i = 1, idx = 0;
        stack<int> s1, s2;
        while (cur <= n || i <= n) {
            if (!s1.empty() && s1.top() == cur) {
                ans[++idx] = 1;
                cur++;
                s1.pop();
            } else if (!s2.empty() && s2.top() == cur) {
                // s2出栈是d操作,但此时如果有合适的a操作可做则做a操作
                if (i <= n && color[i] == 1 && (s1.empty() || s1.top() > a[i])) {
                    ans[++idx] = 0;
                    s1.push(a[i]); i++;
                } else {
                    ans[++idx] = 3;
                    cur++; 
                    s2.pop();
                }
            } else if (i <= n && color[i] == 1) {
                ans[++idx] = 0;
                s1.push(a[i]); i++;
            } else if (i <= n && color[i] == 2) {
                ans[++idx] = 2;
                s2.push(a[i]); i++;
            }
        }
        for (int i = 1; i <= idx; i++)
            printf("%c%c", 'a' + ans[i], i == idx ? '\n' : ' ');
    }
    return 0;
}

标签:二分,return,int,染色,dfs,color,graph,include
From: https://www.cnblogs.com/ronchen/p/18172048

相关文章

  • 整体二分
    整体二分动态排名每次二分复杂度\(O(n\logV)\),问题瓶颈在于有多次询问整体二分一种离线算法,将多个询问一起处理:条件问询可以二分修改之间互不影响修改对答案的贡献和判定次数,时间无关贡献满足结合律,交换律,可加性算法流程核心函数,处理一个区间的询问,他们的......
  • 二分的妙用
    数列分段SectionII链接:https://www.luogu.com.cn/problem/P1182题目描述对于给定的一个长度为\(N\)的正整数数列\(A_{1\simN}\),现要将其分成\(M\)(\(M\leqN\))段,并要求每段连续,且每段和的最大值最小。关于最大值最小:例如一数列\(4\2\4\5\1\)要分成\(3\)段。将......
  • 二分查找
    先给数组排序定义最小点left和最大点right取中间值作为cur循环判断,cur跟target谁更大若cur大了,则减小最大值right为cur-1;反之增加最小值为cur+1直到找到cur下标跟target一样大的情况,就可以返回cur了反之如果一直找不到,直到最小值left>最大值right了,就可以认为数组中没有这......
  • 利用二分法删除数组中元素
    二分法的思想主要是要设定起始值和终点值,计算中值,和给定值进行比较,如果大于给定值,则将中值作为终点值,否则作为起始值,重新计算中值。#include<stdio.h>intmain(){intarray[10]={1,2,3,5,8,15,20,30,100,200};intfirst=0,end=9,middle=(first+end)/2,num,i;s......
  • 二分查找
    1.0二分查找概念keywords:单调区间、最大化最小值(最小化最大值)、时间复杂度O(logn) 1.1二分模板模板来自于AK机大厂笔试星球。1.1.1在非递减数组中找到第一个≥x的数publicintlowerBound(int[]nums,intx){intl=0,r=nums.length-1;while(......
  • 二分查找的左闭右开和左闭右闭写法
    0.参考参考链接:二分查找的左闭右开和左闭右闭写法1.思路0.序言lower_bound查找的是升序序列中的第一个出现target的pos,区间应从右向左收缩。upper_bound查找的是升序序列中的最后一个出现target的pos,区间应从左向右收缩。主循环判断本质目的是为了确保整个区间能够被检索......
  • 2015 ACM ICPC Singapore Regional D(折半枚举+二分)
    D-AssociationofComputerMaintenance题意给定至多350个小于100的质数,对于所有质数之积k将它分解为两个数a和b使得a*b=k。输出最小的a+b,并对1e9+7取模分析首先考虑想如果想让a+b最小,即让abs(a-b)最小。随后根据限制条件k的因子数不超过1e10,容易想到将k拆分成k1和k2,此......
  • P5896 [IOI2016] aliens (斜率优化 dp+wqs 二分)
    我们可以把所有点都对称到主对角线下方。然后每个点如果想被覆盖都会有一个最小的三角形,我们可以贪心的只留必须选的点。如果把剩下的点按\(x\)坐标升序排序,可以发现他们的\(y\)坐标也是升序排序的。记剩余点个数为\(n\),显然每个点都选自己的最小三角形最好。但是有可能\(......
  • 【基础】整体二分
    namespaceMultiBinarySearch{staticconstintMAX_QUERY=2e5+10;structQuery{intid,cnt;//分cnt组时,每组的大小最大有多大?容易知道分的组数越多,其最大的siz会变小。};intans[MAX_QUERY];intcheck(intM){intcnt=0;//实现这个根据......
  • 说说你对二分查找的理解?如何实现?应用场景?
     一、是什么在计算机科学中,二分查找算法,也称折半搜索算法,是一种在有序数组中查找某一特定元素的搜索算法想要应用二分查找法,则这一堆数应有如下特性:存储在数组中有序排序搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束如果某一特定元素大......