hdu 1565 方格取数(1)(最小割,最大点权独立集)

46 篇文章 0 订阅

方格取数(1)

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 4127    Accepted Submission(s): 1578


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

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

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

Sample Input
  
  
3 75 15 21 75 15 28 34 70 5
 

Sample Output
  
  
188
// 点覆盖集:无向图G的一个点集,使得该图中所有边都至少有一个端点在该集合内。 // 最小点权覆盖集:在带点权无向图G中,点权之和最小的覆盖集。 // 点独立集:无向图G的一个点集,使得任两个在该集合中的点在原图中都不相邻。 // 最大点权独立集:在带权无向图G中,点权之和最大的独立集。 // 定理: // 1. 最小点权覆盖集=最小割=最大流 // 2. 最大点权独立集=总权-最小点权覆盖集 // 解题: // 1. 先染色,取一个点染白色,和它相邻的点染黑色 // 2. 每个白点向它相邻的黑点连一条边,容量为 inf (无穷大) // 3. 增加源点S,向每一个白色点连一条边,容量为白点的权 // 4. 增加汇点T,每个黑点向T连一条边,容量为黑点的权
除去最小割中的点,能保证余下的点构成一个点独立集。
最后结果为最大点权独立集的点权总和,即 原图总的点权 - 最小割的点权总和(最大流)
换一种说法,网络流割中的边,表示取数方案中不取的边,可以看成取数方案付出的代价。而最小割,则代表取数方案的最小代价,即取最优解时的代价。这题要求取数时不能取相邻的数,这样相邻两点之间建边容量为INF,代表如果同时选择这两点,代价为INF。
 
AC代码:
#include <iostream>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <queue>
#include <ctime>
#include <algorithm>
#define ll __int64

using namespace std;

const int INF = 1000000000;
const int maxn = 405;

int d[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
struct Edge
{
    int u, v, cap, flow, next;
} et[maxn * maxn];
int low[maxn], cnt[maxn], dis[maxn], cur[maxn], pre[maxn], eh[maxn];
int s, t, n, num;
bool ok(int x, int y)
{
    if(x >= 1 && x <= n && y >= 1 && y <= n) return true;
    return false;
}
void init()
{
    memset(eh, -1, sizeof(eh));
    num = 0;
}
void add(int u, int v, int cap, int flow)
{
    Edge e = {u, v, cap, flow, eh[u]};
    et[num] = e;
    eh[u] = num++;
}
void addedge(int u, int v, int cap)
{
    add(u, v, cap, 0);
    add(v, u, 0, 0);
}
int isap(int s, int t, int nv)
{
    int u, v, now, flow = 0;
    memset(cnt, 0, sizeof(cnt));
    memset(dis, 0, sizeof(dis));
    memset(low, 0, sizeof(low));
    for(u = 0; u <= nv; u++) cur[u] = eh[u];
    low[s] = INF, cnt[0] = nv, u =s;
    while(dis[s] < nv)
    {
        for(now = cur[u]; now != -1; now = et[now].next)
            if(et[now].cap - et[now].flow && dis[u] == dis[v = et[now].v] + 1) break;
        if(now != -1)
        {
            cur[u] = pre[v] = now;
            low[v] = min(et[now].cap - et[now].flow, low[u]);
            u = v;
            if(u == t)
            {
                for(; u != s; u = et[pre[u]].u)
                {
                    et[pre[u]].flow += low[t];
                    et[pre[u]^1].flow -= low[t];
                }
                flow += low[t];
                low[s] = INF;
            }
        }
        else
        {
            if(--cnt[dis[u]] == 0) break;
            dis[u] = nv, cur[u] = eh[u];
            for(now = eh[u]; now != -1; now = et[now].next)
                if(et[now].cap - et[now].flow && dis[u] > dis[et[now].v] + 1)
                    dis[u] = dis[et[now].v] + 1;
            cnt[dis[u]]++;
            if(u != s) u = et[pre[u]].u;
        }
    }
    return flow;
}
int main()
{
    int val;
    while(~scanf("%d", &n))
    {
        init();
        s = 0;
        t = n * n + 1;
        int sum = 0;
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
            {
                scanf("%d", &val);
                if((i + j) &1) addedge((i - 1) * n + j, t, val);
                else addedge(s, (i - 1) * n + j, val);
                sum += val;
            }

        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
            {
                if((i + j) &1)
                {
                    for(int k = 0; k < 4; k++)
                    {
                        int nx = i + d[k][0], ny = j + d[k][1];
                        if(ok(nx, ny))
                        {
                            addedge((nx - 1) * n + ny, (i - 1) * n + j, INF);
                        }
                    }
                }
            }
        printf("%d\n", sum - isap(s, t, t + 1));
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值