cunzai_zsy0531

关注我

P3356 火星探险问题 题解

Post time: 2020-03-03 19:32:32

题目链接

网络流题目套路:

  1. 分析如何通过最大流/最小(大)费用来表示题目中变量。
  2. 根据 1 的分析建图,保证这个表示是正确的。
  3. 跑 HLPP/Dinic/ISAP(最大流)或者 mcmf。

一般的网络流题目,难点在第 2 步上。也不排除有些题目第一步的表示非常妙。

这道题是典型的建图题,我们会发现题目中两个变量:采集标本的数量和探测车的数量。很容易想到用最大流控制探测车,用最大费用求出最大标本数量。

考虑建图:

  1. \(S\to X_{1,1}\) 流量探测车数量 \(n\),费用 \(0\),表示初始状态。
  2. 拆点,\(X_{i,j}\)\(Y_{i,j}\),分别表示走到此点和走过此点。若此点有石头,则 \(X_{i,j}\to Y_{i,j}\),流量 \(1\),费用 \(1\),表示取了这块石头。另外,只要这个点可以走,就连一条边\(X_{i,j}\to Y_{i,j}\),流量 \(INF\),费用 \(0\),表示走过这个点。
  3. 点之间的连线,\(Y_{i,j}\)\(X_{i+1,j}\)\(X_{i,j+1}\) 分别表示向南走和向东走。
  4. \(Y_{p,q}\to T\),流量 \(INF\),费用 \(0\),表示完成任务。

这样我们会发现,我们通过建图中的 (1) 控制了探测车数量,然后通过 (2)(3) 完成了取石头的构图。接下来就是皆大欢喜的跑 mcmf 啦!

点击查看代码
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#define nm (n*m)
using namespace std;
const int N=35*35*2+5,M=35+5,INF=0x3f3f3f3f;//注意两点:maxn一定要开够;INF要小于INTmax/2,不然加起来可能会爆
struct Edge{int u,v,f,w,nxt;}e[N*2];
int h[N],pre[N],flow[N],dist[N],last[N];
bool vis[N];
int a[M][M],p[M][M];
int W,n,m,mc,mf,s,t,tot=1;
inline void add(int u,int v,int f,int w){//双向边
	e[++tot]=(Edge){u,v,f,w,h[u]};h[u]=tot;
	e[++tot]=(Edge){v,u,0,-w,h[v]};h[v]=tot;
}
bool spfa(){
	for(int i=1;i<=nm*2+2;++i) dist[i]=-INF;
	memset(flow,0x3f,sizeof(flow));
	memset(vis,0,sizeof(vis));
	queue<int>q;
	q.push(s);
	dist[s]=0;
	vis[s]=1;
	pre[t]=-1;//判断能否到达汇点
	while(!q.empty()){
		int u=q.front();
		q.pop();
		vis[u]=0;
		for(int i=h[u];i;i=e[i].nxt){
			int v=e[i].v,f=e[i].f,w=e[i].w;
			if(f&&dist[v]<dist[u]+w){//改符号成最大费用最大流
				dist[v]=dist[u]+w;
				flow[v]=min(flow[u],f);
				last[v]=i;
				pre[v]=u;
				if(!vis[v]){
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
	return pre[t]!=-1;//这里就是上面提到的判断
}
void mcmf(){
	while(spfa()){
		mf+=flow[t];
		mc+=flow[t]*dist[t];
		int now=t;
		while(now!=s){//增广之后的流量处理
			e[last[now]].f-=flow[t];
			e[last[now]^1].f+=flow[t];//不要忘了反向边加流量
			now=pre[now];
		}
	}
}
void dfs(int x,int cnt){//输出路径函数,顺着方向dfs即可,注意是判断反向边流量来判定这条边有没有走过,走了几次。
	if(x==nm) return; 
	int u,v;
	u=x+nm;
	if(x%m){
		v=x+1;
		int i=h[u];
		while(i&&e[i].v!=v) i=e[i].nxt;
		if(e[i^1].f){
			e[i^1].f--;
			printf("%d 1\n",cnt);
			dfs(v,cnt);
			return;
		}
	}
	if(x<=nm-m){
		v=x+m;
		int i=h[u];
		while(i&&e[i].v!=v) i=e[i].nxt;
		if(e[i^1].f){
			e[i^1].f--;//走到一次就减一,控制住了这个点走过探测车的数量。
			printf("%d 0\n",cnt);
			dfs(v,cnt);
			return;
		}
	}
}
int main(){
	scanf("%d%d%d",&W,&m,&n);s=nm*2+1,t=nm*2+2;
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			scanf("%d",&a[i][j]);
			p[i][j]=(i-1)*m+j;
		}
	}
    //下面就是建边了
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			if(a[i][j]==1) continue;
			add(p[i][j],p[i][j]+nm,INF,0);
			if(a[i][j]==2) add(p[i][j],p[i][j]+nm,1,1);//有石头,加一条取石头边
		}
	}
	add(s,1,W,0);
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			if(a[i][j]==1) continue;
			if(j+1<=m&&a[i][j+1]!=1) add(p[i][j]+nm,p[i][j+1],INF,0);
			if(i+1<=n&&a[i+1][j]!=1) add(p[i][j]+nm,p[i+1][j],INF,0);
		}
	}
	add(nm*2,t,INF,0);
	mcmf();
	for(int i=1;i<=mf;++i) dfs(1,i);
	return 0;
} 
posted @ 2022-04-21 12:07  cunzai_zsy0531  阅读(29)  评论(0编辑  收藏  举报