SDFZOJ#338 不稳定的导弹系统

题目简述

$n×m$ 的网格图,每一小格代表一个单位区域,单位区域上要么是空地,要么是导弹发射器,要么有一定数量的叛军,一个导弹发射器只能消灭一个方向的一个单位区域的叛军,求最多可以消灭多少叛军。保证不会有导弹发射器能炸到另一个导弹发射器,导弹的飞行路线不能有交叉。

$n,m≤50$

思路

先不考虑导弹路线不能交叉的限制,对于每一个导弹发射器就可以选择点权最大的那个点。由于每个区域只可能被一个横向和一个纵向的导弹打到(因为题目题面保证了「导弹发射器不能炸到导弹发射器」),那么我们可以把每一个点拆成两个点。

将一开始的答案ans设为每个导弹可以打到的最大点权之和,这个答案大概率是不合法的。将每个点的点权重新赋为一开始的答案减去这个点原来的点权,即ans-a[i][j],这样做的意义是当将导弹发射器与某个点匹配的时候,只需要在原答案的基础上减去这个点的新点权就可以了,至此我们通过转化将原问题变成一个「减去尽量小的点权并使答案合法」的问题。

建图

对于横点,向下一个点连一条边权为自己新点权的边,对于竖点,下一个点像自己连一条边权为自己新点权的边,可以得到上面这样的图。原来的减去一些点的点权就可以转化成删掉一些边。

不合法

按照上图建立超级源点和超级汇点可以发现,当网络图中存在一条通路时,两发导弹的路线会相交,此时的删边时不合法的。例如上面的删边方式代表的是两发导弹分别选择了两条边到达的点,这样它们的路线会相交,此时网络图中也确实存在一条通路(蓝色)。 最终这个问题变成了要删掉边权和尽量小的边并使图中不存在通路,也就是求最小割。

至于为什么横竖的边方向不同?还记得我们一开始将一个点拆成了一个横点和一个竖点吗?这样连边可以时同一个点的横点和竖点形成通路,可以避免两个导弹同时选择这个点的情况,同时也避免了相交。

Code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<climits>
#define int long long
#define ID1(x,y) (((x-1)*m+y)*2)
#define ID2(x,y) (((x-1)*m+y)*2-1)
using namespace std;
int n, m, ans, a[55][55], head[55*55*2], cntEdge=1, dis[55*55*2];
bool visited[55*55*2];
struct Edge{
	int destination, nextEdge, value;
}edge[55*55*4*2];

inline int Read(){
	int num = 0, op = 1;
	char ch = getchar();
	while(!isdigit(ch)){
		if(ch == '-') op = -1;
		ch = getchar();
	}
	while(isdigit(ch)){
		num = num * 10 + ch - '0';
		ch = getchar();
	}
	return op * num;
}

inline void AddEdge(int u, int v, int w){
	cntEdge++;
	edge[cntEdge].destination = v;
	edge[cntEdge].nextEdge = head[u];
	edge[cntEdge].value = w;
	head[u] = cntEdge;
	return;
}

inline void Link(int u, int v, int w){
	AddEdge(u, v, w);
	AddEdge(v, u, 0);
	return;
}

inline bool Check(){
	int res = INT_MAX;
	for(int i=0; i<=n*m*2+1; i++){
		if(visited[i]){
			for(int j = head[i]; j; j = edge[j].nextEdge){
				int to = edge[j].destination, val = edge[j].value;
				if(val && !visited[to]){
					res = min(res, dis[to]+1-dis[i]);
				}
			}
		}
	}
//	printf("%lld\n", res);
	if(res == INT_MAX) return false;
	for(int i=0; i<=n*m*2+1; i++){
		if(visited[i]) dis[i] += res;
	}
	return true;
}

inline int Update(int x, int y, int z){
	visited[x] = true;
//	printf("%lld\n", z);
	if(x == y){
		ans -= z;
		return z;
	}
	for(int i = head[x]; i; i = edge[i].nextEdge){
		int &to = edge[i].destination, &val = edge[i].value;
		if(!visited[to] && val && dis[x] == dis[to] + 1){
			int res = Update(to, y, min(z,val));
			if(res){
				edge[i].value -= res;
				edge[i^1].value += res;
				return res;
			}
		}
	}
	return 0;
}

signed main(){
	n = Read(), m = Read();
	for(int i=1; i<=n; i++){
		for(int j=1; j<=m; j++){
			a[i][j] = Read();
			Link(ID1(i,j), ID2(i,j), INT_MAX);
		}
	}
	for(int i=1; i<=n; i++){
		for(int j=1; j<=m; j++){
			int mx = 0;
			switch(a[i][j]){
				case -1:{
					Link(0, ID1(i,j), INT_MAX);
					for(int k=i; k>=1; k--)
						mx = max(mx, a[k][j]);
					for(int k=i; k>=2; k--)
						Link(ID1(k,j), ID1(k-1,j), mx-max(a[k][j],0ll));
					break;
				}
				case -2:{
					Link(0, ID1(i,j), INT_MAX);
					for(int k=i; k<=n; k++)
						mx = max(mx, a[k][j]);
					for(int k=i; k<n; k++)
						Link(ID1(k,j), ID1(k+1,j), mx-max(a[k][j],0ll));
					break;
				}
				case -3:{
					Link(ID2(i,j), n*m*2+1, INT_MAX);
					for(int k=j; k>=1; k--)
						mx = max(mx, a[i][k]);
					for(int k=j; k>=2; k--)
						Link(ID2(i,k-1), ID2(i,k), mx-max(a[i][k],0ll));
					break;
				}
				case -4:{
					Link(ID2(i,j), n*m*2+1, INT_MAX);
					for(int k=j; k<=m; k++)
						mx = max(mx, a[i][k]);
					for(int k=j; k<m; k++)
						Link(ID2(i,k+1), ID2(i,k), mx-max(a[i][k],0ll));
					break;
				}
			}
			ans += mx;
			Link(ID1(i,j), ID2(i,j), INT_MAX);
		}
	}
	do{
		memset(visited, false, sizeof(visited));
//		printf("O");
		while(Update(0, n*m*2+1, INT_MAX)){
//			printf("h");
			for(int i=0; i<=n*m*2+1; i++)
				visited[i] = false;
		}
//		printf("\n");
//		printf("%lld\n", ans);
	}while(Check());
	printf("%lld", ans);
	return 0;
}
Sorry potatoler, but I cannot recognize you.
Built with Hugo
Theme Stack designed by Jimmy