zoukankan      html  css  js  c++  java
  • 方格取数问题

    题目描述

    在一个有 m*n 个方格的棋盘中,每个方格中有一个正整数。现要从方格中取数,使任意 2 个数所在方格没有公共边,且取出的数的总和最大。试设计一个满足要求的取数算法。对于给定的方格棋盘,按照取数要求编程找出总和最大的数。

    输入输出格式

    输入格式:
    第 1 行有 2 个正整数 m 和 n,分别表示棋盘的行数和列数。接下来的 m 行,每行有 n 个正整数,表示棋盘方格中的数。

    输出格式:
    程序运行结束时,将取数的最大总和输出

    输入输出样例

    输入样例#1:
    3 3
    1 2 3
    3 2 3
    2 3 1

    输出样例#1:
    11

    分析:这是一道经典的网络流模板题,我们先将格子黑白染色,假使我们选中一个格子,那么与之冲突的格子一定是不同颜色的,所以我们把所有黑格向源点连边,容量是格子里所带的权值,所有的白格向汇点连边,容量也是权值;每个黑格向与之有冲突的的白格连边,跑一遍最大流就好了,然而需要注意的是:最后的答案是所有权值之和-最大流
    我在这里尝试的解释一下这么做的正确性,虽然算的是最大流,但换句话说就是最小割,我们割掉的边是权值最小的遍,所以最后的答案要用所有权值之和-最大流(最小割)

    这里写代码片
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<cmath>
    #include<queue>
    
    using namespace std;
    
    const int INF=10000001;
    struct node{
        int x,y,v,next;
    }; 
    node way[8001];
    int n,m,st[4001],map[201][201],s,t;
    int deep[4001],tot=-1,tt=0;
    bool p[4001];
    int zz[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
    
    int add(int u,int vv,int w)
    {
        tot++;
        way[tot].x=u;   //正向边 边权=容量-当前流量 
        way[tot].y=vv;
        way[tot].v=w;
        way[tot].next=st[u];
        st[u]=tot;
        tot++;
        way[tot].x=vv;  //反向边  边权=当前流量
        way[tot].y=u;
        way[tot].v=0;
        way[tot].next=st[vv];
        st[vv]=tot;
    } 
    
    int bh(int h,int z)
    {
        return m*(h-1)+z;
    }
    
    void lianbian()
    {
        int i,j;
        s=0;
        t=n*m+1;
        for (i=1;i<=n;i++)
        {
            for (j=1;j<=m;j++)
            {
                if ((i+j)%2==1)
                {
                   add(s,bh(i,j),map[i][j]);
                   for (int l=0;l<4;l++)
                   {
                        int xx=i+zz[l][0];
                        int yy=j+zz[l][1];
                        if (xx>0&&yy>0&&xx<=n&&yy<=m)
                            add(bh(i,j),bh(xx,yy),INF);
                   }
                }
                else
                   add(bh(i,j),t,map[i][j]);
            }
        }
    }
    
    int bfs(int s,int t)  //分层   搜索时搜的都是正向边 
    {
        int i;
        memset(deep,0x7f,sizeof(deep));
        memset(p,1,sizeof(p));  //判断点是否遍历过 
        queue<int> q;
        q.push(s);
        deep[s]=1;
        p[s]=0;
        while (!q.empty())
        {
            int r=q.front();
            q.pop();
            for (i=st[r];i!=-1;i=way[i].next)
               if (p[way[i].y]&&way[i].v)  //判断一下此边是否可走 
               {
                    p[way[i].y]=0;
                    deep[way[i].y]=deep[r]+1;
                    q.push(way[i].y);
               }
        }
        return deep[t]<0x7f;  //如果deep[t]==0x7f,说明无路可增广了 
    }
    
    int dfs(int now,int t,int limit)  //limit是可以增广的流量,因为要取min,所以传进来的是INF 
    {
        int i;
        if (limit==0||now==t)  //剪枝 
           return limit;
        int flow=0;  //这张增广网上的总流量
        int f; 
        for (i=st[now];i!=-1;i=way[i].next)
        {
            if (deep[way[i].y]==deep[now]+1&&(f=dfs(way[i].y,t,min(limit,way[i].v))))   
            {  //递归放在if中!!! 
                flow+=f;
                limit-=f;  //?记住就好了 
                way[i].v-=f;
                way[i^1].v+=f;
                if (!limit) break;  //!!! 
            }
        }
        return flow;
    }
    
    int dinic()
    {
        int ans=0;
        while (bfs(s,t)) //有继续增广的潜力 
           ans+=dfs(s,t,INF);  
        return ans;
    }
    
    int main()
    {
        memset(st,-1,sizeof(st));
        scanf("%d%d",&n,&m);
        for (int i=1;i<=n;i++)
           for (int j=1;j<=m;j++)
              scanf("%d",&map[i][j]),tt+=map[i][j];
        lianbian();
        printf("%d",tt-dinic());
        return 0;
    }
  • 相关阅读:
    也谈一下关于兔子的问题
    关于sql函数返回表
    关于1000瓶水的问题
    WWF的疑问
    天干和地支
    在若干个整数中找到相加之和为某个整数的所有组合的算法
    输出一个数组的全排列
    新的博客, 新的里程
    学习搜索引擎心得(10.2511.25)
    下一个阶段(用C++重写Lucene的计划)
  • 原文地址:https://www.cnblogs.com/wutongtong3117/p/7673655.html
Copyright © 2011-2022 走看看