题解 DTOJ #1150. 应急预警系统(system)

欢迎访问 My Luogu Space

【题目大意】

有一栋 h ∗ n ∗ m h*n*m hnm 个单元的大楼。

现在每个单元必须需要向周围六个相邻单元(如果在大楼的某一侧则会少一些相邻单元)中的两个连接安全疏散通道,每两个单元之间连接通道的费用不一定相同。

询问连接的最小费用。如果无法连接请输出"No Answer!"。


【题解】

费用流跑二分图匹配

很直接就可以想到网络流,只不过建边需要一点想法。

考虑到是两个单元相互连接,我们可以将所有的单元进行黑白染色,然后源点连向黑色,白色连向汇点,流量都为 2 2 2,费用为 0 0 0,。于每组相邻单元一定是不同颜色,那么就从黑色连向白色,流量为 1 1 1,费用为题目所给的费用。

跑最小费用最大流的二分图即可,输出时用流量判断是否能够连接即可。

注意:染色时有小技巧。


【代码】

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100000+10;
const int INF = 0x3f3f3f3f;
struct EDGE{int to, w, f;}e[MAXN*2];

int h, n, m, s, t=100000, ans, ctn;
int id[20][20][20], f[MAXN];
int ct=1, hed[MAXN], nex[MAXN*2], cur[MAXN*2], dis[MAXN];

void add(int a, int b, int w, int f){
	e[++ct].to=b, e[ct].w= w, e[ct].f=f, nex[ct]=hed[a], hed[a]=ct;
	e[++ct].to=a, e[ct].w=-w,e[ct].f=0, nex[ct]=hed[b], hed[b]=ct;	
}
bool spfa(){
	memset(dis, INF, sizeof dis);
	queue<int> Q; Q.push(s);
	dis[s] = 0;
	while(!Q.empty()){
		int x=Q.front(); Q.pop();
		f[x] = 0;
		for(int i=hed[x]; i; i=nex[i]){
			if(e[i].f && dis[x]+e[i].w<dis[e[i].to]){
				dis[e[i].to] = dis[x]+e[i].w;
				if(!f[e[i].to])
					f[e[i].to] = 1, Q.push(e[i].to);
			}
		}
	}
	return dis[t] != INF;
}
int dfs(int x, int flow){
	if(x == t) return flow;
	int sum = 0; f[x] = 1;
	for(int i=cur[x]; i; i=nex[i]){
		cur[x] = i;
		if(!f[e[i].to] && e[i].f && dis[e[i].to]==dis[x]+e[i].w){
			int p = dfs(e[i].to, min(flow-sum, e[i].f));
			sum += p;
			e[i].f -= p;
			e[i^1].f += p;
			ans += p*e[i].w;
			if(sum == flow) break;
		}
	}
	f[x] = 0;
	return sum;
}
int zkw(){
	int sum = 0;
	while(spfa()){
		memcpy(cur, hed, sizeof hed);
		sum += dfs(s, INF);
	}
	return sum;
}
int main(){
	scanf("%d%d%d", &h, &n, &m);
	for(int i=0; i<h; ++i)
		for(int j=0; j<n; ++j)
			for(int k=0; k<m; ++k){
				id[i][j][k] = ++ctn;
				if((i+j+k)&1) add(s, id[i][j][k], 0, 2);
				else add(id[i][j][k], t, 0, 2);
			}
	for(int t=1; t<=h*n*m; ++t){
		int i, j, k, c1, c2, c3;
		scanf("%d%d%d%d%d%d", &i, &j, &k, &c1, &c2, &c3);
		if((i+j+k)&1){
			if(id[i+1][j][k]) add(id[i][j][k], id[i+1][j][k], c3, 1);
			if(id[i][j+1][k]) add(id[i][j][k], id[i][j+1][k], c1, 1);
			if(id[i][j][k+1]) add(id[i][j][k], id[i][j][k+1], c2, 1);
		}
		else{
			if(id[i+1][j][k]) add(id[i+1][j][k], id[i][j][k], c3, 1);
			if(id[i][j+1][k]) add(id[i][j+1][k], id[i][j][k], c1, 1);
			if(id[i][j][k+1]) add(id[i][j][k+1], id[i][j][k], c2, 1);
		}
	}
	if(zkw() == h*n*m) printf("%d", ans);
	else puts("No Answer!");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值