【网络流24题】火星探险(拆点+费用流)

8 篇文章 0 订阅
7 篇文章 0 订阅

传送门

    火星探险
    题意:给定网格,其中放有只能被拿取一次的岩石,求从左上角格点到右下角格点的k条路径,使得拿取的岩石总数最多。

I think

    最大费用流,将点拆至x,y两个集合.对于格点i在x集合中的对应点,向i在y集合中的对应点连容量为Inf,费用为0的边,若其置放岩石,再向y集中的对应点了连容量为1费用为1的边,向其右边和下边的在x集中的对应点连容量为Inf,费用为0的边.
    乍一看这道题和深海机器人很像,于是用同样的做法第一遍WA.为什么?深海机器人题给的本来就是边权,但本题相当于给出的是点权,点权只能够取一次,于是需要通过拆点来限流.

Code

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

const int sm = 2460;
const int sn = 9900;
const int Inf = 0x3f3f3f3f;

int K,Q,P,S,T,tot=1,tmp;
int mk[36][36],mp[36][36];
int pre[sm],vis[sm],cst[sm];
int to[sn],nxt[sn],hd[sm],c[sn],_c[sn],f[sn];

int Min(int x,int y) { return x<y?x:y; }
bool chk(int x,int y) { return (x<=Q&&x>=1&&y<=P&&y>=1&&mk[x][y]!=1); }
void Add(int u,int v,int x,int y) {
    to[++tot]=v,nxt[tot]=hd[u],hd[u]=tot,_c[tot]=c[tot]=x,f[tot]=y;
    to[++tot]=u,nxt[tot]=hd[v],hd[v]=tot,_c[tot]=c[tot]=0,f[tot]=-y;
}
void SPFA() {
    int df,t;
    queue<int>Que;
    while(true) {
        for(int i=1;i<=T;++i) pre[i]=0,cst[i]=-Inf;
        Que.push(S),cst[S]=0;
        while(!Que.empty()) {
            t=Que.front(),Que.pop();
            vis[t]=0;
            for(int i=hd[t];i;i=nxt[i])
                if(c[i]>0&&cst[to[i]]<cst[t]+f[i]) {
                    cst[to[i]]=cst[t]+f[i];
                    pre[to[i]]=i;
                    if(!vis[to[i]]) {
                        vis[to[i]]=1;
                        Que.push(to[i]);
                    }
                }
        }
        if(cst[T]==-Inf) break; 
        df=Inf; 
        for(int i=T;i!=S;i=to[pre[i]^1])
            df=Min(df,c[pre[i]]);
        for(int i=T;i!=S;i=to[pre[i]^1])
            c[pre[i]]-=df,c[pre[i]^1]+=df;
    }
}
void Print(int num,int x) {
    if(x==mp[Q][P]) return;
    for(int i=hd[x];i;i=nxt[i]) 
        if(_c[i]!=c[i]&&i%2==0) {
            if(to[i]!=x+tmp) {
                if(to[i]==x-tmp+1) printf("%d %d\n",num,1);
                else printf("%d %d\n",num,0);
            }
            c[i]++,Print(num,to[i]);
            return;
        }
}
int main() {
    int cnt=0; 
    scanf("%d%d%d",&K,&P,&Q);
    for(int i=1;i<=Q;++i)
        for(int j=1;j<=P;++j) {
            scanf("%d",&mk[i][j]);
            mp[i][j]=++cnt;
        }

    tmp=Q*P,S=(Q*P<<1)+1,T=S+1;
    Add(S,mp[1][1],K,0);
    Add(mp[Q][P]+tmp,T,K,0);
    for(int i=1;i<=Q;++i)
        for(int j=1;j<=P;++j) {
            if(mk[i][j]==1) continue;
            if(mk[i][j]==2) Add(mp[i][j],mp[i][j]+tmp,1,1);
            Add(mp[i][j],mp[i][j]+tmp,Inf,0);
            if(chk(i+1,j)) Add(mp[i][j]+tmp,mp[i+1][j],Inf,0);
            if(chk(i,j+1)) Add(mp[i][j]+tmp,mp[i][j+1],Inf,0);
        }
    SPFA();
    for(int i=1;i<=K;++i)
        Print(i,mp[1][1]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值