2014杭电多校联合第一场Jump费用流(最小K路径覆盖)

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

题意:有一个n*m的网格,每一个格子里有一个0到9的数字。你的初始能量是0。你最多能玩K次游戏,

每次游戏开始时你可以选择任意一个之前为走过的网格作为起点。然后,你可以选择在当前网格下,

向右或者向下跳跃至之前为到达过的网格。每次游戏过程中,你可以跳跃任意你想跳跃的次数,

只要没有违反前述规则。如果你从网格(x1,y1)跳跃至网格(x2,y2),那么你就将消耗|x1-x2|+|y1-y2|-1的能量。

能量值可以为负值,然而,在一次跳跃过程中,如果你起点的数字和终点的数字都是相同的S,

那么你将增长S能力值。求你能获得的最大的能量值。注意你必须到达每一个网格,但你并不需要玩完K次游戏。


思路:最小K路径覆盖。额,,,直接用费用流写的。

【以下转自杭电官方解题报告】

最小K路径覆盖的模型,用费用流或者KM算法解决,构造二部图,X部有N*M个节点,源点向X部每个节点连一条边,

流量1,费用0Y部有N*M个节点,每个节点向汇点连一条边,流量1,费用0,如果X部的节点x可以在一步之内到达Y部的节点y

那么就连边x->y,费用为从x格子到y格子的花费能量减去得到的能量,流量1再在X部增加一个新的节点,表示可以从任意节点出发K次,

源点向其连边,费用0,流量K,这个点向Y部每个点连边,费用0,流量1最这个图跑最小费用最大流,如果满流就是存在解,

反之不存在,最小费用的相反数就是可以获得的最大能量

关于加粗的那句话:这个表示从任意节点出发K次的构图方式学习了。

另外,因为每个点都要走遍,所以最终满流才表示存在解,否则无解。

二分图的构造方式,还是比较常见的。


#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<queue>
#define MAX 250
#define INF 0x7fffffff
using namespace std;
struct Point
{
    int x,y,val;
}p[MAX];
struct Edge
{
    int to;
    int cap;
    int cost;
    int next;
}edge[400000];
int n,m,k;
int st,ed,K_virtual;
int cnt,head[MAX];
int dist[MAX],vist[MAX],pre[MAX],pos[MAX];
void add(int u,int v,int cap,int cost)
{
    edge[cnt].to=v;
    edge[cnt].cap=cap;
    edge[cnt].cost=cost;
    edge[cnt].next=head[u];
    head[u]=cnt++;

    edge[cnt].to=u;
    edge[cnt].cap=0;
    edge[cnt].cost=-cost;
    edge[cnt].next=head[v];
    head[v]=cnt++;
}
void input()
{
    int num=0;
    char str[15];
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++)
    {
        scanf("%s",str);
        for(int j=0;j<m;j++)
        {
            p[++num].x=i;
            p[num].y=j+1;
            p[num].val=str[j]-'0';
        }
    }
    cnt=0;
    memset(head,-1,sizeof(head));
    st=0;
    ed=2*n*m+1+1;
    K_virtual=2*n*m+1;              //构造任意出发点
    add(st,K_virtual,k,0);
    for(int i=1;i<=num;i++)
    {
        add(K_virtual,i+num,1,0);       //模拟任意起始点
        add(st,i,1,0);
        add(i+num,ed,1,0);
        for(int j=1;j<=num;j++)
        {
            if((p[i].x==p[j].x&&p[i].y<p[j].y)||(p[i].y==p[j].y&&p[i].x<p[j].x))//能一部走到的,连边,权值为消耗的能量减去得到的能量
            {
                int energy=abs(p[i].x-p[j].x)+abs(p[i].y-p[j].y)-1;
                if(p[i].val==p[j].val)
                {
                    energy=energy-p[i].val;
                }
                add(i,j+num,1,energy);
            }
        }
    }
}

void mcmf()
{
    int u,maxflow=0,mincost=0;
    for(;;)
    {
        for(int i=st;i<=ed;i++)
        {
            vist[i]=0;
            pre[i]=-1;
            dist[i]=INF;
        }
        dist[st]=0;
        vist[st]=1;
        pre[st]=st;
        queue<int>q;
        q.push(st);
        while(!q.empty())
        {
            u=q.front();
            q.pop();
            vist[u]=0;
            for(int i=head[u];i!=-1;i=edge[i].next)
            {
                int v=edge[i].to;
                if(edge[i].cap>0&&dist[v]>dist[u]+edge[i].cost)
                {
                    dist[v]=dist[u]+edge[i].cost;
                    pre[v]=u;
                    pos[v]=i;
                    if(!vist[v])
                    {
                        vist[v]=1;
                        q.push(v);
                    }
                }
            }
        }
        if(dist[ed]==INF)break;
        maxflow++;
        mincost+=dist[ed];
        for(u=ed;u!=st;u=pre[u])
        {
            edge[pos[u]].cap--;
            edge[pos[u]^1].cap++;
        }
    }
    if(maxflow==n*m)                //满流才有解
        printf("%d\n",-mincost);
    else
        printf("-1\n");
}
int main()
{
    int t;
    scanf("%d",&t);
    for(int i=1;i<=t;i++)
    {
        input();
        printf("Case %d : ",i);
        mcmf();
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值