BZOJ 1001 狼抓兔子 平面图上的最大流--跑最短路

首先应该能看出来这道题求的是最小割(最大流)。然后再看出来这道题的图是个平面图,然后就可以关于这个平面图建出它的对偶图,在这个对偶图上跑最短路就是答案了。直接跑网络流的Dinic应该是T的。

然而,这需要知道什么是平面图,以及如何构建这个对偶图。

边的交点都是顶点的图就是平面图。注意完全图K4也是平面图,详细可见百度百科:http://baike.baidu.com/view/143351.htm 或维基百科:https://zh.wikipedia.org/wiki/%E5%B9%B3%E9%9D%A2%E5%9B%BE_(%E5%9B%BE%E8%AE%BA)

另外,关于平面图f = m - n + 2的证明(f,平面数,n,定点数,m,边数),我想说一下自己的想法:平面图的平面数即这个图把所在平面分成的部分数。那么,树固然是只有一个平面——无界面。我们往一棵树上加边,每加一条边,都会多一个“小环”,就多“一块”,即多一个面。一个连通图,肯定可以从其中摘出一棵树。所以f = m - n + 2就很好理解了。

对于一个平面图G,它的对偶图G*,满足:
G中的一个面对应G*中的一个点;
G中的一条边,连接两个面f1, f2对应G*中的一条边(f1*, f2*)
G中的一条边,仅仅属于一个面f,对应G*中的一条回边(f*,f*)
(注意G中的一条边最多属于两个面)

这样就可以建出对偶图G*,它和G有这样的关系:
f(G) = n(G*), m(G) = m(G*)
G*中的环一一对应对应G中的割

知道了这样的一个性质,那么就可以做到通过求G*中的一个最小环来求G中的最小割了(G中的边的容量对应G*中的边权)。

总而言之,一个图G满足以下条件:
1.是平面图
2.源点s和汇点t都在无界面的边界上
之后就可以这样建立对偶图G*
1.添加一条边(s,t),这样会多出一个面,令这个面为图G*中的起点s*
2.令无界面为图G*中的终点t*
3.通过之前描述的建图方式连边
4.删除1.中添加的边对应的G*中的s*到t*的边

稍微想一下便知道,现在直接跑最短路算法即可得出答案。

QaQ,这道题也是比较水,一眼就能看出是平面图,无需判断,而且很容易建立对偶图G*。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;

int get ()
{
    char c = getchar(); int x = 0;
    while (c < '0' || c > '9') c = getchar();
    while (c <= '9' && c >= '0') x = x*10+c-48, c = getchar();
    return x;
}

int n, m, MIN = 1<<30, hen[1005][1005], zon[1005][1005], xie[1005][1005];
int e, h[2000005], nx[6000005], to[6000005], w[6000005];
int s, t, d[2000005];
bool inq[2000005];
queue <int> q;

void addedge (int a, int b, int v)
{
    nx[++e] = h[a], h[a] = e, to[e] = b, w[e] = v;
    nx[++e] = h[b], h[b] = e, to[e] = a, w[e] = v;
}

void Initialize ()
{
    n = get(), m = get();
    for (int i = 1; i <= n; i++) 
    for (int j = 1; j <  m; j++) 
        MIN = min(MIN, hen[i][j] = get());
    for (int i = 1; i <  n; i++)
    for (int j = 1; j <= m; j++) 
        MIN = min(MIN, zon[i][j] = get());
    for (int i = 1; i <  n; i++)
    for (int j = 1; j <  m; j++) 
        MIN = min(MIN, xie[i][j] = get());
}

void Get_newG ()
{
    if (n == 1 || m == 1) return ;
    s = 0, t = (n-1)*(m-1)*2+1;
    for (int i = 1; i < m; i++)
    {
        addedge (s, i, hen[1][i]);
        addedge (t, t-m+i, hen[n][i]);
    }
    for (int i = 2; i < n; i++)
    for (int j = 1; j < m; j++)
    {
        int a = (i-2)*(m-1)*2+m-1+j;
        addedge (a, a+m-1, hen[i][j]);
    }
    for (int i = 1; i < n; i++)
    {
        addedge (t, (i-1)*(m-1)*2+m, zon[i][1]);
        addedge (s, (i-1)*(m-1)*2+m-1, zon[i][m]);
    }
    for (int i = 1; i < n; i++)
    for (int j = 2; j < m; j++)
    {
        int a = (i-1)*(m-1)*2+j-1;
        addedge (a, a+m, zon[i][j]);
    }
    for (int i = 1; i < n; i++)
    for (int j = 1; j < m; j++)
    {
        int a = (i-1)*(m-1)*2+j;
        addedge (a, a+m-1, xie[i][j]);
    }
}

void Spfa ()
{
    if (n == 1 || m == 1) {printf ("%d\n", MIN); return;}
    memset (d, 0x3f, sizeof d);
    d[s] = 0; q.push(s);
    while (!q.empty())
    {
        int x = q.front(); q.pop(); inq[x] = 0;
        for (int i = h[x]; i; i = nx[i])
        if (d[to[i]] > d[x]+w[i]){
            d[to[i]] = d[x] + w[i];
            if (!inq[to[i]]) q.push(to[i]);
            inq[to[i]] = 1;
        }
    }
    printf ("%d\n", d[t]);
}

int main ()
{
    Initialize();
    Get_newG();
    Spfa();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值