首页 > 搜索 > BFS搜索 > HDU 1565 方格取数(1)-二分图-[解题报告] C++
2013
12-12

HDU 1565 方格取数(1)-二分图-[解题报告] C++

方格取数(1)

问题描述 :

给你一个n*n的格子的棋盘,每个格子里面有一个非负数。
从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取的数所在的2个格子不能相邻,并且取出的数的和最大。

输入:

包括多个测试实例,每个测试实例包括一个整数n 和n*n个非负数(n<=20)

输出:

对于每个测试实例,输出可能取得的最大的和

样例输入:

3
75 15 21 
75 15 28 
34 70 5 

样例输出:

188

HDU_1565

一开始没有什么思路,后来看了别人的解题报告说要去构造一个二分图转化成最小割去做。

后来我又琢磨了一下,大概原理是这样的,如果把原图按i+j的奇偶性分成两部分(也就是染成国际象棋棋盘那样),并且相邻的格子之间连一条边的话,就变成了求最大权独立集了,而最大权独立集又等于SUM减去最小点权覆盖集,这样我们就转化成去求最小点权覆盖集了。而如果我们构造一个超级源点和二分图左边所有点相连并且边权为格子的权值,同时另二分图右边所有点和构造的一个超级汇点相连并且边权为格子的权值,并且把二分图中间的边的边权设为INF,这样求出的最小割就是最小点权覆盖集。

因为如果是最小点权覆盖集的话,拿掉这些点及其覆盖的边,原图一定不通(如果与源点或者汇点相连的边都被覆盖,那么二分图所有边一定会被覆盖,因此只要能够覆盖二分图的边即可),同时又因为是最小点权覆盖,所以最小点权覆盖集是原图的最小割。

#include<stdio.h>
#include<string.h>
#define MAXD 3000
#define MAXM 26000
#define INF 100000000
int N, SUM;
int first[MAXD], next[MAXM], u[MAXM], v[MAXM], flow[MAXM], e;
int s[MAXD], d[MAXD], q[MAXD], work[MAXD];
int dx[] = {-1, 1, 0, 0}, dy[] = {0, 0, -1, 1};
void add(int a, int b, int w)
{
    u[e] = a;
    v[e] = b;
    flow[e] = w;
    next[e] = first[a];
    first[a] = e;
    e ++;
}
int init()
{
    int i, j, k, a;
    if(scanf("%d", &N) != 1)
        return 0;
    memset(first, -1, sizeof(first));
    e = SUM = 0;
    for(i = 1; i <= N; i ++)
        for(j = 1; j <= N; j ++)
        {
            scanf("%d", &a);
            SUM += a;
            if((i + j) % 2 == 0)
            {
                add(0, i * N + j, a);
                add(i * N + j, 0, 0);
                for(k = 0; k < 4; k ++)
                {
                    int x = i + dx[k];
                    int y = j + dy[k];
                    if(x >= 1 && x <= N && y >= 1 && y <= N)
                    {
                        add(i * N + j, x * N + y, INF);
                        add(x * N + y, i * N + j, 0);
                    }
                }
            }
            else
            {
                add(i * N + j, 1 , a);
                add(1, i * N + j, 0);
            }
        }
    return 1;
}
int bfs()
{
    int i, j, rear;
    memset(d, -1, sizeof(d));
    d[0] = 0;
    rear = 0;
    q[rear ++] = 0;
    for(i = 0; i < rear; i ++)
        for(j = first[q[i]]; j != -1; j = next[j])
            if(d[v[j]] == -1 && flow[j])
            {
                d[v[j]] = d[q[i]] + 1;
                if(v[j] == 1)
                    return 1;
                q[rear ++] = v[j];
            }
    return 0;
}
int dfs(int cur, int a)
{
    if(cur == 1)
        return a;
    for(int &i = work[cur]; i != -1; i = next[i])
        if(flow[i] && d[v[i]] == d[cur] + 1)
            if(int t = dfs(v[i], a < flow[i] ? a : flow[i]))
            {
                flow[i] -= t;
                flow[i ^ 1] += t;
                return t;
            }
    return 0;
}
int dinic()
{
    int res = 0, t;
    while(bfs())
    {
        memcpy(work, first, sizeof(first));
        while(t = dfs(0, INF))
            res += t;
    }
    return res;
}
int main()
{
    while(init())
    {
        int res = dinic();
        printf("%d\n", SUM - res);
    }
    return 0;
}


解题报告转自:http://www.cnblogs.com/staginner/archive/2011/10/14/2212506.html


  1. 第二个方法挺不错。NewHead代表新的头节点,通过递归找到最后一个节点之后,就把这个节点赋给NewHead,然后一直返回返回,中途这个值是没有变化的,一边返回一边把相应的指针方向颠倒,最后结束时返回新的头节点到主函数。

  2. 很高兴你会喜欢这个网站。目前还没有一个开发团队,网站是我一个人在维护,都是用的开源系统,也没有太多需要开发的部分,主要是内容整理。非常感谢你的关注。

  3. 很高兴你会喜欢这个网站。目前还没有一个开发团队,网站是我一个人在维护,都是用的开源系统,也没有太多需要开发的部分,主要是内容整理。非常感谢你的关注。