P3866 [TJOI2009] 战争游戏 题解

传送门:P3866 [TJOI2009] 战争游戏

Solution

首先考虑如何建图。

先看建源点和汇点的原因:

  • 建源点:有若干支不同的军队需要拦截,所以要将他们所在点都与源点连接,以跑网络流。
  • 建汇点:有若干个通向图外的可行边界点,也要将它们都与汇点连在一起,以跑网络流。

这样一来,源点的汇点的建图方式就可以轻易得到了:

  • 源点:自源点向所有军队的所在点相连,流量为 i n f inf inf
  • 汇点:将所有可以通行的图内边界点与汇点相连,流量为 i n f inf inf

如此,解决此题的具体方式也就出来了:求最小割。

上文所述,将军队都与源点相连,可行边界点都与汇点相连,则要想阻止军队出图,就是要割去一些带权值的边,既能使源点和汇点不连通,又要使割去边的权值之和最小

因为最大流等于最小割,所以这道题就转化为求最大流。那么上述的边权在下文就变成了流量。

再看如何建图的中间部分:

  1. 很明显,要将所有的可行点(即该点没有障碍物)和它四周可以到达的点连边,流量为 i n f inf inf

  2. 对于空地,发现它有一个轰炸值(即轰炸所费炸yao数),特别地,对于军队所在点,轰炸值为 0。按上文所述,要想将一个点权转化为边权,也就是一条边的流量,那么方法呼之欲出:拆点。

  3. 所以,我们将每一个可行点拆成两个点,入点和出点,并在它们之间连边。若点为空地,则边的流量即为空地的轰炸值,这样即可限制通过该空地点的流量;若点为军队所在点,则边的流量为 i n f inf inf

  4. 最后再来说入点和出点的连边:对于所有可行点,使用出点向四周可行点的入点连边;对于军队所在点,则自源点与该点的入点连边。

建图就说完了,剩下的就只有最大流板子了。

特别:注意点的编号的处理。

Code

更多详情见代码及注释。

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

#define rep(i, a, b) for(int i = a; i <= b; ++i)
#define maxn 2005
#define maxm 40005
#define inf 2147483647
int n, m, d, nm;
int c[maxn][maxn], cntc, l[maxm], r[maxm], ans;
int s, t, p[maxn][maxn], tot;
int dep[maxm];
int hd[maxm], cur[maxm], cnt = 1;
int fx[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
struct node{
	int to, nxt, w;
}e[maxm << 1];

inline void add(int u, int v, int w)
{
	e[++cnt].to = v;
	e[cnt].nxt = hd[u], e[cnt].w = w;
	hd[u] = cnt;
	e[++cnt].to = u;
	e[cnt].nxt = hd[v], hd[v] = cnt;
}

inline int id(int x, int y)/*点的编号的处理*/
{
	return (x - 1) * m + y;
}

inline bool bfs()
{
	queue <int> q;
	memset(dep, 0, sizeof dep);
	dep[s] = 1, q.push(s);
	while(!q.empty())
	{
		int u = q.front();
		q.pop();
		for(int v, i = hd[u]; i; i = e[i].nxt)
		{
			if(!dep[v = e[i].to] and e[i].w)
			{
				dep[v] = dep[u] + 1;
				q.push(v);
			}                              
		}
	}
	return dep[t] ? true : false;
}

inline int dinic(int u, int in)
{
	if(u == t or !in) return in;
	int out = 0;
	for(int v, &i = cur[u]/*cur[u] 随着 i 的变化而变化*/; i and in/*注意判容量剩余*/; i = e[i].nxt)
	        /*当前弧优化*/ 
	{
		if(dep[v = e[i].to] == dep[u] + 1 and e[i].w)
		{
			int res = dinic(v, min(in, e[i].w));
			e[i].w -= res, e[i ^ 1].w += res;
			in -= res, out += res;
		}
	}
	if(!out) dep[u] = 0;
	return out;
}

int main()
{
	scanf("%d %d", &n, &m);
	nm = n * m, t = nm * 2 + 1;
	rep(i, 1, n) rep(j, 1, m) scanf("%d", &c[i][j]);
	rep(i, 1, n) rep(j, 1, m) if(c[i][j] != -1)
	{
		if(!c[i][j])//若该点为军队所在点 
			add(id(i, j), id(i, j) + nm, inf)/*拆点*/, add(s, id(i, j), inf)/*军队所在点和源点相连*/;
		else /*若该点为空地*/add(id(i, j), id(i, j) + nm, c[i][j])/*拆点*/;
		int nw = id(i, j) + nm;//当前入点相对应的出点 
		if(i == 1 or j == 1 or i == n or j == m) add(nw, t, inf);//边界点和汇点相连 
		rep(k, 0, 3)//向点的四周可行点连边 
		{
			int xx = i + fx[k][0], yy = j + fx[k][1];
			if(xx < 1 or yy < 1 or xx > n or yy > m or c[xx][yy] == -1) continue;
			add(nw/*当前点出点*/, id(xx, yy)/*四周可行点入点*/, inf);
		}
	}
	while(bfs())//Dinic板子 
	{
		rep(i, s, t) cur[i] = hd[i];//当前弧优化-数组初始化 
		ans += dinic(s, inf);
	}
	printf("%d\n", ans);
	return 0; 
}

感谢阅读。

辛苦管理员审核,若有问题烦请指出。

posted @ 2022-03-25 07:25  pldzy  阅读(31)  评论(0)    收藏  举报