zoukankan      html  css  js  c++  java
  • luogu2774 方格取数问题 二分图最小权点覆盖集

    题目大意:在一个有 m*n 个方格的棋盘中,每个方格中有一个正整数。现要从方格中取数,使任意 2 个数所在方格没有公共边,输出这些数之和的最大值。

    思路:这种各个点之间互相排斥求最大值的题,往往需要利用上网络流最小割的性质。我们把方格中的所有数字都选上,看看把哪些格子抠掉,能使数值和的减少量最少。

    每个格子看作一个节点,其向四周的格子代表的节点连边。现要求一个节点的集合,使得这些点与所有边相连,求点权之和最小值。这就是最小权点覆盖集问题。

    要想使该问题有解,往往要将图中的节点分为两个集合,一个集合内的任意两个节点之间没有边相连,也就是说图中所有边两头的节点必须属于不同的集合。解决该问题方法为将S与一个集合中的所有点相连,将另一个集合中的所有点与汇点相连,原图中的边容量设为无穷大,然后跑一遍最大流即可。

    理解:每条连接两个集合的边两边的节点我们只要选一个抠掉,两个集合即可彻底分开。这条边的容量是无穷大,即可保证两个 连接两个集合的边的两端节点 与 源汇 连的边中 只会容量小的那个满流,表示选择了这条节点。这样,此时的最大流(最小割)便是最小点权之和。

    本题中,我们发现如果把方格按照国际象棋棋盘那样的方式决定节点的集合,恰好满足要求。

    #include <cstdio>
    #include <cassert>
    #include <cstring>
    #include <queue>
    #include <algorithm>
    #include <cmath>
    using namespace std;
    
    #define LOOP(i, n) for(int i=1; i<=n; i++)
    const int MAX_NODE = 5100, MAX_EDGE = MAX_NODE * 100, INF = 0x3f3f3f3f;
    
    struct Dinic
    {
        struct Node;
        struct Edge;
    
        struct Node
        {
            Edge *Head, *DfsFrom;
            int Level;
        }_nodes[MAX_NODE];
        int _vCount;
        Node *Start, *Target;
    
        struct Edge
        {
            Node *To;
            Edge *Next, *Rev;
            int Cap;
            Edge(Node *to, Edge *next, int cap):To(to),Next(next),Cap(cap){}
        }*_edges[MAX_EDGE];
        int _eCount;
    
        void Init(int vCount, int sId, int tId)
        {
            _vCount = vCount;
            Start = sId + _nodes;
            Target = tId + _nodes;
            _eCount = 0;
        }
    
        Edge *AddEdge(Node *from, Node *to, int cap)
        {
            Edge *e = _edges[++_eCount] = new Edge(to, from->Head, cap);
            from->Head = e;
            return e;
        }
    
        void Build(int uId, int vId, int cap)
        {
            Node *u = uId + _nodes, *v = vId + _nodes;
            Edge *e1 = AddEdge(u, v, cap), *e2 = AddEdge(v, u, 0);
            e1->Rev = e2;
            e2->Rev = e1;
        }
    
        bool Bfs()
        {
            static queue<Node*> q;
            LOOP(i, _vCount)
                _nodes[i].Level = 0;
            Start->Level = 1;
            q.push(Start);
            while (!q.empty())
            {
                Node *u = q.front();
                q.pop();
                for (Edge *e = u->Head; e; e = e->Next)
                {
                    if (!e->To->Level && e->Cap)
                    {
                        e->To->Level = u->Level + 1;
                        q.push(e->To);
                    }
                }
            }
            return Target->Level;
        }
    
        int Dfs(Node *cur, int limit)
        {
            if (cur == Target)
                return limit;
            if (limit == 0)
                return 0;
            int curTake = 0;
            for (Edge *e = cur->DfsFrom; e; cur->DfsFrom = e = e->Next)
            {
                if (e->To->Level == cur->Level + 1 && e->Cap)
                {
                    int nextTake = Dfs(e->To, min(limit - curTake, e->Cap));
                    e->Cap -= nextTake;
                    e->Rev->Cap += nextTake;
                    curTake += nextTake;
                }
                if (limit - curTake==0)
                    break;
            }
            return curTake;
        }
    
        int Proceed()
        {
            int ans = 0;
            while (Bfs())
            {
                LOOP(i, _vCount)
                    _nodes[i].DfsFrom = _nodes[i].Head;
                ans += Dfs(Start, INF);
            }
            return ans;
        }
    }g;
    
    int main()
    {
    #ifdef _DEBUG
        freopen("c:\noi\source\input.txt", "r", stdin);
    #endif
        const int direct[][2] = { {-1,0},{0,1},{1,0},{0,-1} };
        int totCol, totRow, sId, tId, totSum = 0;
        static int matrix[110*110];
        scanf("%d%d", &totCol, &totRow);
        sId = totCol*totRow + 1;
        tId = totCol*totRow + 2;
        g.Init(tId, sId, tId);
        LOOP(col, totCol)
            LOOP(row, totRow)
        {
            int cur = (col - 1)*totRow + row;
            scanf("%d", &matrix[cur]);
            totSum += matrix[cur];
            if ((row + col - 1) % 2)
                g.Build(sId, cur, matrix[cur]);
            else
                g.Build(cur, tId, matrix[cur]);
        }
        LOOP(col, totCol)
            LOOP(row, totRow)
            if ((row + col -1 ) % 2)
            {
                int cur = (col - 1)*totRow + row;
                for (int i = 0; i < 4; i++)
                {
                    int col2 = col + direct[i][0], row2 = row + direct[i][1];
                    if (col2 >= 1 && col2 <= totCol&&row2 >= 1 && row2 <= totRow)
                    {
                        int next = totRow*(col2 - 1) + row2;
                        g.Build(cur, totRow*(col2 - 1) + row2, INF);
                    }
                }
            }
        int subt = g.Proceed();
        printf("%d
    ", totSum - subt);
        return 0;
    }
    View Code

    注意:

    1.判断方格的上下左右这方面,尽量分别用两个变量表示行和列。直观,不容易出错。

    2.先系统连与源汇相连的边,再连两个集合间的边。错误做法:站在一个集合上,找能连到另一个集合的节点的边,将其构造,然后将令那个另一个集合的节点与汇点相连。因为一个集合的点有多条边,每次把另一个集合的节点与汇点相连造成了很多重边。

  • 相关阅读:
    JSP一个简单的项目实现教程
    多个Excel文件快速导入到DB里面
    NotePad++左侧导航
    简单实用JSTL标签库
    Eclipse导入现有项目
    Java工具Eclipse
    winform窗体只能放大不能缩小
    ref out

    数组
  • 原文地址:https://www.cnblogs.com/headboy2002/p/8449111.html
Copyright © 2011-2022 走看看