hdu 4862 Jump(最大费用流)

探讨在一个n*m网格中,通过不超过K次选择起始点进行跳跃,以获得最大能量值的问题。采用最小费用流算法,巧妙地通过拆点等技巧解决遍历所有网格点并优化能量获取的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Jump

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 1436    Accepted Submission(s): 610



Problem Description
There are n*m grids, each grid contains a number, ranging from 0-9. Your initial energy is zero. You can play up to K times the game, every time you can choose any one of the grid as a starting point (but not traveled before) then you can choose a grid on the right or below the current grid to jump, but it has not traveled before. Every time you can jump as many times as you want, as long as you do not violate rules. If you are from (x1, y1) to (x2, y2), then you consume |x1-x2|+|y1-y2|-1 energies. Energy can be negative.
However, in a jump, if you start position and end position has same numbers S, then you can increase the energy value by S.
Give me the maximum energy you can get. Notice that you have to go each grid exactly once and you don’t have to play exactly K times.
 

Input
The first line is an integer T, stands for the number of the text cases.
Then T cases followed and each case begin with three numbers N, M and K. Means there are N rows and M columns, you have K times to play.
Then N lines follow, each line is a string which is made up by M numbers.
The grids only contain numbers from 0 to 9.
(T<=100, N<=10,M<=10,K<=100)
 

Output
Each case, The first you should output “Case x : ”,(x starting at 1),then output The maximum number of energy value you can get. If you can’t reach every grid in no more than K times, just output -1.
 

Sample Input
5 1 5 1 91929 1 5 2 91929 1 5 3 91929 3 3 3 333 333 333 3 3 2 333 333 333
 

Sample Output
Case 1 : 0 Case 2 : 15 Case 3 : 16 Case 4 : 18 Case 5 : -1
 

题意:给你一个n*m的矩阵,你可以从选择任何一个点,从这个点向下和向右跳(可空格跳),跳的次数不做限制,同一个点不能跳两次跳的两点间的消耗曼哈顿距离-1能量,若两点数字相同,则怎加数字的能量值,问在不超过k次选择点,能到达所有点的最大能量值。

思路:
很明显的最小费用流,同一个点只能跳一次,明显要拆点。关键在于到达所有的点应该如何在图中表示。
1.首先要定义一个起点(初始位置的集合),源点点到这个起点的流量为k.
2.起点连接点的右点,流量为1,花费0;
3.起点连接每个点的左点,流量为1,花费0;
4.能到达的点连接,流量1,花费算一下就好;
5.每个点右点连接汇点,流量为1.

代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
#define inf 0x7fffffff
using namespace std;
typedef long long ll;
const int maxn=1111;
int n,m,from,to;
struct node
{
    int v,flow,cost;
    int next;
} edge[maxn*40];
int head[maxn],edgenum;
ll dis[maxn],pre[maxn],pid[maxn],vis[maxn];
char num[maxn][maxn];
int t;
void add(int u,int v,int flow,int cost)
{
    edge[edgenum].v=v ;
    edge[edgenum].flow=flow ;
    edge[edgenum].cost=cost ;
    edge[edgenum].next=head[u];
    head[u]=edgenum++;

    edge[edgenum].v=u ;
    edge[edgenum].flow=0;
    edge[edgenum].cost=-cost ;
    edge[edgenum].next=head[v];
    head[v]=edgenum++;
}

ll spfa()
{
    for (int i=0 ; i<=to ; i++)
    {
        dis[i]=-inf;
        vis[i]=0;
    }
    queue<int> Q;
    Q.push(from);
    dis[from]=0;
    vis[from]=1;
    while (!Q.empty())
    {
        int u=Q.front() ;
        Q.pop() ;
        vis[u]=0;
        for (int i=head[u] ; i!=-1 ; i=edge[i].next)
        {
            int v=edge[i].v;
            if (edge[i].flow>0 && dis[v]<dis[u]+edge[i].cost)
            {
                dis[v]=dis[u]+edge[i].cost;
                pre[v]=u;
                pid[v]=i;
                if (!vis[v])
                {
                    vis[v]=1;
                    Q.push(v);
                }
            }
        }
    }
    return dis[to];
}

int mincost()
{
    int aug=0,maxflow=0;
    ll ans=0,tmp=0;
    while (1)
    {
        tmp=spfa();
        if (tmp==-inf) break;
        aug=inf;
        for (int i=to ; i!=from ; i=pre[i])
        {
            if (edge[pid[i] ].flow<aug)
                aug=edge[pid[i] ].flow;
        }
        for (int i=to ; i!=from ; i=pre[i])
        {
            edge[pid[i] ].flow -= aug;
            edge[pid[i]^1 ].flow += aug;
        }
        maxflow += aug;
        ans += tmp;
    }
    if(maxflow!=n*m)
        printf("Case %d : -1\n",t);
    else
        printf("Case %d : %I64d\n",t,ans);
}

int main()
{
    int k;
    int T;
    scanf("%d",&T);
    for(t=1;t<=T;t++)
    {
        memset(head,-1,sizeof(head));
        edgenum=0;
        scanf("%d%d%d",&n,&m,&k);
        for(int i=0;i<n;i++)
           scanf("%s",num[i]);
        for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
        {
            num[i][j]-='0';
        }
        from=0;
        to=n*m*2+2;
        int bin=n*m*2+1;
        add(0,bin,k,0);
        for(int i=0;i<n;i++)
        for(int j=0;j<m;j++)
        {
            add(0,i*m+j+1,1,0);
            add(bin,i*m+j+1+n*m,1,0);
            add(i*m+j+1+n*m,to,1,0);
            for(int kk=i+1;kk<n;kk++)
            {
                if(num[i][j]==num[kk][j])
                    add(i*m+j+1,kk*m+j+1+n*m,1,i+1-kk+num[i][j]);
                else
                    add(i*m+j+1,kk*m+j+1+n*m,1,i+1-kk);
            }
            for(int kk=j+1;kk<m;kk++)
            {
                if(num[i][j]==num[i][kk])
                    add(i*m+j+1,i*m+kk+1+n*m,1,j+1-kk+num[i][j]);
                else
                    add(i*m+j+1,i*m+kk+1+n*m,1,j+1-kk);
             //   printf("%d %d ~%d~\n",num[i][j],num[i][kk],j+1-kk+num[i][j]);
            }
        }
        mincost();
    }
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值