洛谷1330 封锁阳光大学 二分图判断

题目

描述

洛谷1330 封锁阳光大学 二分图判断_第1张图片

输入输出样例

输入
3 3
1 2
1 3
2 3
输出(注意,下面的Impossible无换行)
Impossible

输入
3 2
1 2
2 3
输出(注意,下面的1无换行)
1

题解

每个连通图最多只有一个对应的二分图

  • 假设一个连通图可以构造出一个二分图,尝试对该二分图进行变形以构造新的二分图
  • 更改其中一个点的位置,则与该点相邻接的点也要改变位置,如此下去(类似bfs,一层层推进、改变位置)。因为这是一个连通图,所以这个点可以通往图中所有的点,所以所有的点都需要改变位置,也就等价于无改变

为什么只有二分图可以被河蟹封锁

  • 使用两种颜色对该图的端点进行染色,只要边的两个端点异色,则可以被封锁,并且,也只有两个端点异色的图才可以被河蟹封锁
  • 将所有同色的点加进一个集合里,从而这个集合内的点不相邻,并且每条边关联的两个点都属于不同的集合,所以这是一个二部图——也是该连通图的唯一的二部图

如何判别二分图

  • 染色法判断
  • 并查集判断

代码(并查集)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

#define MAX 10010
struct UFset {
    // fa[i] record i's father
    // rank[i] record i's rank, begin from 0
    UFset() { init(); }
    int fa[MAX], rank[MAX], sum[MAX];
    int getRoot(int x) {
        return fa[x] == x ? x : fa[x] = getRoot(fa[x]);
    }
    void unionSet(int x, int y) {
        int xf = getRoot(x);
        int yf = getRoot(y);
        if (xf != yf) {
            if (rank[xf] > rank[yf]) {
                fa[yf] = xf;
                sum[xf] += sum[yf];
            } else {
                fa[xf] = yf;
                sum[yf] += sum[xf];
            }
            rank[yf] += rank[xf] == rank[yf];
        }
    }

  private:
    void init() {
        for (int i = 1; i < MAX; i++) {
            fa[i] = i;
        }
        for (int i = 1; i < MAX; i++) {
            this->sum[i] = 1;
        }
        memset(this->rank, 0, sizeof(this->rank));
    }
};

UFset set;
int os[MAX];
// os[i]记录i点所在的二分图的那个集合的对面那个集合
//(比如说该二分图有A、B两个集合,i在A集合,那么os[i]就是B集合)
// 记录的可以不是该集合的root,也可以仅仅是该集合内的点
bool vis[MAX];
int main() {
    int n, m, a, b;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; i++) {
        scanf("%d%d", &a, &b);
        int af = set.getRoot(a);
        int bf = set.getRoot(b);
        if (af == bf) {
            printf("Impossible");
            return 0;
            // needn't to read all input
            // for that there is only one input
        }
        if (os[a]) {
            set.unionSet(os[a], b);
        }
        if (os[b]) {
            set.unionSet(os[b], a);
        }
        os[a] = bf;
        os[b] = af;
    }
    int ans = 0;
    /*
     * 对于孤立点,没有边,所以os[i]为0
     * 这种点不能算进ans里,但是我们难以排除掉这些点,
     * 所以最好就是sum[0]设置为0——为什么如此?因为孤立点i的对面os[i]为0
     * 所以求sum(i) sum(os[i])中如果sum(os[i])为0,那么ans就会选择那个0的去加
     */
    for (int i = 1; i <= n; i++) {
        int h = set.getRoot(i);
        if(os[i]==0)
            continue;
        int p = set.getRoot(os[i]);
        if (vis[h] || vis[p])
            continue;
        vis[h] = true;
        vis[p] = true;
        ans += set.sum[h] < set.sum[p] ? set.sum[h] : set.sum[p];
    }
    printf("%d", ans);
}

解释

  • os[i] 不仅仅记录的是 i 所在的集合对面的那个集合,而且记录的了以前是否已经有边的顶点是i ——如果是,那么os[i] 就是非零(因为题目输入的端点的取值是 [1,n] [ 1 , n ]
  • 可以想象有n个孤立的点,来一条边(i,j)就把用os[i], os[j]记录i、j已经来过。并且os[i], os[j]还记录了i、j跟那个集合连接了。然后如果来的某条边连接的点以前已经跟某个集合连接了,就把os[i], j 所在的集合或是os[j], i 所在的集合并起来。从而保证了循环过程中每个集合最多只跟另一个集合连接。
  • 还需要注意,可能有孤立点——也就是os[j] 为0的点,孤立点不需要河蟹

你可能感兴趣的:(算法,ACM)