HDU -- 4862 Jump(费用流)

给一个n*m的矩阵,矩阵中每个位置有一个0~9的数字,每次选中一个点开始跳,只能向下或向右跳(不能既向下又向右跳)。每跳一次要消耗的能量是这两点的哈密顿距离-1,如果起跳点和着陆点的数字相同,就会得到与数字相同的能量。每次可以连续的跳。最多选取k个点开始跳,并且每个点都要跳到,每个点只能跳一次,求能量的最大值(初始能量为0)。

比赛时根本没看懂这题啥意思=。=

http://acm.hdu.edu.cn/showproblem.php?pid=4862

这题是最大费用最大流。

首先我们建一个源点,并把每个点拆成两个点i和i'。

从源点向每个i连一条容量为1,费用为0的边(每个点只能做一次起点,做起点不需要费用)

在建一个汇点,从每个i'向汇点连一条容量为1费用为0的边

如果可以从i跳向j,就建一条从i'到j的边,容量为1,费用为两点间的哈密顿距离-1(如果两点的数字相同,再减去这个数字)(因为是求最大费用,所以费用要加能量的负值)

从i向i'连一条容量为1,费用为-INF的边。因为流到这个点以后,一定要从这个点流出,再流到下一个点。这样在每个点的出点和入店间加一条费用为-INF的边,因为这条边的费用是最小的,所以会优先流过这个点。(注意:这里的INF不能和求最短路时的inf一样!QAQ)

那么如何保证不超过k次呢?

我们再建一个超级源点,从这个超级源点,向源点连一条容量为k费用为0的边,这样就保证的最多选k次起点。

但是!题目中还说,不一定要跳满k次,只要能量最大。

所以在求得最小费用的减小时,就退出。这样就保证了能量最大。

接下来就是如何判断是否每个点都已经跳过了。

因为每个点的入点和出点之间有一条费用为-INF的边,一共n*m个点就相当于费用多了-n*m*INF,所以就要看-mincost - m*n*INF是否小于0。

也就是-mincost / INF < m*n是否成立,如果成立就说明没有遍历所有的点输出-1.

否则答案应该是-mincost - m*n*INF,即-mincost % INF.

//#pragma comment(linker,"/STACK:1024000000,1024000000")
#include <map>
#include <set>
#include <cmath>
#include <queue>
#include <stack>
#include <cstdio>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

typedef double DB;
typedef long long ll;
typedef pair<int, int> PII;

#define pb push_back
#define MP make_pair
#define lson l, m, rt << 1
#define rson m + 1, r, rt << 1 | 1

const DB eps = 1e-6;
const int inf = ~0U>>1;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1000000007;
const int maxn = 1000 + 10;


/*mincost 为最小费用。若要求最大费用,加边时将费用取反,最后-mincost是最大费用*/
///先调用init,然后while(Find());
const int maxv = 1000 + 10;///顶点数
const int maxe = 1000000 + 10;///边数
struct node{
    int v, w, cap, next; ///cap = 容量, w = 费用
}edge[maxe];
struct fnode{
    int e, cap, w;///上一条边,容量,费用
}fn[maxv];
int head[maxv], cnt, st, ed, maxflow, mincost;
bool vis[maxv];
void addedge(int u, int v, int cap, int w){
    edge[cnt].v = v; edge[cnt].w = w; edge[cnt].cap = cap; edge[cnt].next = head[u]; head[u] = cnt++;
    edge[cnt].v = u; edge[cnt].w = -w; edge[cnt].cap = 0; edge[cnt].next = head[v]; head[v] = cnt++;
//    printf("u:%d, v:%d, cap:%d, w:%d, cnt:%d\n", u, v, cap, w, cnt - 1);
}
bool Find(){///找最短路
    memset(vis, false, sizeof(vis));
    for(int i=st; i<=ed; i++) fn[i].w = inf;
    fn[st].w = 0;
    vis[st] = 1; fn[st].cap = inf;
    queue<int> Q;
    Q.push(st);
    while(!Q.empty()){
        int now = Q.front(); Q.pop();
        vis[now] = 0;
        for(int i=head[now]; ~i; i=edge[i].next){
            int k = edge[i].v;
            if(edge[i].cap && fn[now].w + edge[i].w < fn[k].w){
                fn[k].w = fn[now].w + edge[i].w;
                fn[k].cap = min(fn[now].cap, edge[i].cap);
                fn[k].e = i;
                if(!vis[k]){
                    vis[k] = 1; Q.push(k);
                }
            }
        }
    }
    if(fn[ed].w == inf) return 0;
    int tmp = mincost + fn[ed].cap * fn[ed].w;
    if(tmp > mincost) return 0;//这里就是判断最小费用是不是在增加
    mincost += fn[ed].cap * fn[ed].w;
    maxflow += fn[ed].cap;
    int i = ed;
    while(i != st){
        edge[fn[i].e].cap -= fn[ed].cap;
        edge[fn[i].e ^ 1].cap += fn[ed].cap;
        i = edge[fn[i].e ^ 1].v;
    }
    return 1;
}
void init(int source, int sink){
    memset(head, -1, sizeof(head)); cnt = 0;
    st = source; ed = sink;///源点和汇点
    maxflow = mincost = 0;///最大流和最小费用
}


int T, n, m, k, a[maxn][maxn];
char str[maxn];
int main(){
    scanf("%d", &T);
    for(int cas=1; cas<=T; cas++){
        scanf("%d%d%d", &n, &m, &k);
        init(0, 2 * n * m + 2);
        addedge(st, 1, k, 0);
        for(int i=0; i<n; i++){
            scanf("%s", str);
            for(int j=0; j<m; j++){
                a[i][j] = str[j] - '0';
                addedge(1, i * m + j + 2, 1, 0);
                addedge(m * n + i * m + j + 2, ed, 1, 0);
                addedge(i * m + j + 2, m * n + i * m + j + 2, 1, -100000);
            }
        }
        for(int i=0; i<n; i++)
        for(int j=0; j<m; j++){
            for(int k=i+1; k<n; k++){
                int tmp = k - i - 1;
                if(a[i][j] == a[k][j]) tmp -= a[i][j];
                addedge(n * m + i * m + j + 2, k * m + j + 2, 1, tmp);
            }
            for(int k=j+1; k<m; k++){
                int tmp = k - j - 1;
                if(a[i][j] == a[i][k]) tmp -= a[i][j];
                addedge(n * m + i * m + j + 2, i * m + k + 2, 1, tmp);
            }
        }
        while(Find());
        printf("Case %d : ", cas);
        if(-mincost / 100000 < n * m) puts("-1");
        else printf("%d\n", -mincost % 100000);
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值