luogu P3356 火星探险问题(网络流24题 拆点 + 最小费用流 + 路径输出)

这是一个关于火星探测车路径规划的问题,目标是最大化到达传送器的探测车数量和采集到的石块数量。问题描述了一个p×q的网格,其中0表示无障碍,1表示障碍,2表示石块。探测车只能向南或向东移动,且每个石块只能被采集一次。通过建立最大流最小割模型,可以找到最优的移动策略。给定的代码实现了一个SPFA算法来解决这个问题。
摘要由CSDN通过智能技术生成

题目描述

火星探险队的登陆舱将在火星表面着陆,登陆舱内有多部障碍物探测车。登陆舱着陆后,探测车将离开登陆舱向先期到达的传送器方向移动。

探测车在移动中还必须采集岩石标本。每一块岩石标本由最先遇到它的探测车完成采集。每块岩石标本只能被采集一次。岩石标本被采集后,其他探测车可以从原来岩石标本所在处通过。探测车不能通过有障碍的地面。

本题限定探测车只能从登陆处沿着向南或向东的方向朝传送器移动,而且多个探测车可以在同一时间占据同一位置。如果某个探测车在到达传送器以前不能继续前进,则该车所采集的岩石标本将全部损失。

用一个 p \times qp×q 网格表示登陆舱与传送器之间的位置。登陆舱的位置在 (x_1,y_1)(x1​,y1​) 处,传送器的位置在 (x_py_q)(xp​yq​) 处。

\begin{bmatrix} (x_1,y_1) & (x_2,y_1) & \dots & (x_{p-1},y_1) & (x_p,y_1) \\ (x_1,y_2) & (x_2,y_2) & \dots & (x_{p-1},y_2) & (x_p,y_2) \\ \dots & \dots & \dots & \dots & \dots \\ (x_1,y_{q-1}) & (x_2,y_{q-1}) & \dots & (x_{p-1},y_{q-1}) & (x_p,y_{q-1}) \\ (x_1,y_q) & (x_2,y_q) & \dots & (x_{p-1},y_q) & (x_p,y_q) \end{bmatrix}⎣⎢⎢⎢⎢⎢⎡​(x1​,y1​)(x1​,y2​)…(x1​,yq−1​)(x1​,yq​)​(x2​,y1​)(x2​,y2​)…(x2​,yq−1​)(x2​,yq​)​……………​(xp−1​,y1​)(xp−1​,y2​)…(xp−1​,yq−1​)(xp−1​,yq​)​(xp​,y1​)(xp​,y2​)…(xp​,yq−1​)(xp​,yq​)​⎦⎥⎥⎥⎥⎥⎤​

给定每个位置的状态,计算探测车的最优移动方案,使到达传送器的探测车的数量最多,而且探测车采集到的岩石标本的数量最多。

输入格式

第一行为探测车数 nn,接下来两行分别为 p,qp,q。

接下来的 qq 行是表示登陆舱与传送器之间的位置状态的 p \times qp×q 网格。
用三种数表示火星表面位置的状态:00 表示平坦无障碍,11 表示障碍,22 表示石块。

输出格式

每行包含探测车号和一个移动方向,00 表示向南移动,11 表示向东移动。

输入输出样例

输入 #1复制

2
10
8
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 1 1 0 0 0
0 0 0 1 0 2 0 0 0 0
1 1 0 1 2 0 0 0 0 1
0 1 0 0 2 0 1 1 0 0
0 1 0 1 0 0 1 1 0 0
0 1 2 0 0 0 0 1 0 0
0 0 0 0 0 0 0 0 0 0

输出 #1复制

1 1
1 1
1 1
1 1
1 0
1 0
1 1
1 1
1 1
1 1
1 0
1 0
1 1
1 0
1 0
1 0
2 1
2 1
2 1
2 1
2 0
2 0
2 0
2 0
2 1
2 0
2 0
2 1
2 0
2 1
2 1
2 1

说明/提示

【数据范围】 对于 100\%100% 的数据,1 \le n,p,q \le 351≤n,p,q≤35。

 

 这题面比英文都难看 

解释一下8还是:

p * q的平面,(1, 1)点有 n 个小车,只能向下或向右走,终点为 (p, q)。格点有3个状态,0为平坦,1为障碍物,2为石块。障碍物无法通行,其余地方同一时间内可有多辆小车,石块处可选择采集石块,同一石块只能被采集一次,问小车的移动方案,使得到达终点的小车最多,而且采集到的石块最多

思路:

把小车的数量当作流量,采集石块的数量当作费用(取负值),每个位置拆成一个出点和一个入点,建边:

(1)除障碍物外的所有点,入点连出点,流量为inf,费用为0

(2)有石块的点,入点连出点,流量为1,费用为-1(一块石头最多采集一次,费用为其贡献值 -1)

(3)超级源点连(1, 1)的入点,流量为小车数量,费用为0

(4)(p, q)的出点连超级汇点,流量为小车数量,费用为0

(5)小车向右移动:左边点的出点连右边点的入点,流量为inf,费用为0

(6)小车向下移动:上边点的出点连下边点的入点,流量为inf,费用为0

dfs输出路径

……代码转自https://12349.blog.luogu.org/solution-p3356

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

#define nxt(x) (x+m*n)
#define INF 0x3f3f3f3f
int cur=1,n,m,s,t,mcost,mflow,car,ID;
int head[5005],dis[5005],flow[5005],pre[5005];
int Map[105][105],p[105][105];
struct EDGE{
    int t,next,w,f;
}e[100005];
void add(int a,int b,int w,int f)
{
    cur++;e[cur].t=b;e[cur].next=head[a];e[cur].w=w;e[cur].f=f;head[a]=cur;
    cur++;e[cur].t=a;e[cur].next=head[b];e[cur].w=0;e[cur].f=-f;head[b]=cur;
}

queue < int > q;
bool vis[5005];
bool SPFA(int s,int t)
{
    memset(dis,0x3f,sizeof dis);
    memset(vis,0,sizeof vis);
    dis[s]=0;
    vis[s]=1;
    flow[s]=INF;
    q.push(s);
    while (!q.empty())
    {
        int u=q.front();q.pop();
        vis[u]=false;
        for (int h=head[u];h!=-1;h=e[h].next)
        {
            int v=e[h].t,f=e[h].f;
            if (e[h].w&&dis[u]+f<dis[v])//如果边还有流量就尝试更新
            {
                dis[v]=dis[u]+f;//更新最短路径
                flow[v]=min(flow[u],e[h].w);//尽可能地流水
                pre[v]=h;//记录路径
                if (!vis[v])
                {
                    vis[v]=true;
                    q.push(v);
                }
            }
        }
    }
    return dis[t]!=INF;
}

void Update(int s,int t)
{
    int x=t;
    while (x!=s)
    {
        int i=pre[x];
        e[i].w-=flow[t];
        e[i^1].w+=flow[t];
        x=e[i^1].t;
    }//沿着记录下的路径并进行增广路
    mflow+=flow[t];
    mcost+=flow[t]*dis[t];//累计费用
}
void E_K(int s,int t)
{
    while (SPFA(s,t))//当还有多余流量时
        Update(s,t);
}

void dfs(int x,int y,int u,int k)
{
    int kx,ky,mov;
    for (int h=head[u];h!=-1;h=e[h].next)
    {
        int v=e[h].t;
        if (v==s) continue;
        if (v==t) continue;
        if (v==u-n*m) continue;
        if (!e[h^1].w) continue;
        e[h^1].w--;
        if (v>n*m)
        {
            dfs(x,y,v,k);
            return;
        }
        if (v==p[x][y]+1)
        {
            kx=x;
            ky=y+1;
            mov=1;
        }
        else
        {
            kx=x+1;
            ky=y;
            mov=0;
        }
        printf("%d %d\n",k,mov);
        dfs(kx,ky,v+n*m,k);
        return;
    }
}
int main()
{
    scanf("%d%d%d",&car,&m,&n);
    s=0;t=n*m*2+1;
    memset(head,-1,sizeof head);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
        {
            scanf("%d",&Map[i][j]);
            p[i][j]=++ID;
        }
    if (Map[1][1]!=1) add(s,1,car,0);
    if (Map[n][m]!=1) add(nxt(p[n][m]),t,car,0);
    for (int i=1;i<=n;i++)
        for (int j=1;j<=m;j++)
        {
            if (Map[i][j]==1) continue;
            add(p[i][j],nxt(p[i][j]),INF,0);
            if (Map[i][j]==2)
                add(p[i][j],nxt(p[i][j]),1,-1);

            if (Map[i+1][j]!=1&&p[i+1][j])
            {
                add(nxt(p[i][j]),p[i+1][j],INF,0);
            }
            if (Map[i][j+1]!=1&&p[i][j+1])
            {
                add(nxt(p[i][j]),p[i][j+1],INF,0);
            }
        }
    E_K(s,t);
    for (int i=1;i<=mflow;i++)
        dfs(1,1,1,i);
    return 0;
} 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值