[BZOJ 5217] [Lydsy2017省队十连测] 航海舰队

BZOJ传送门

题目描述

Byteasar 组建了一支舰队!他们现在正在海洋上航行着。

海洋可以抽象成一张 n × m n×m n×m 的网格图,其中有些位置是 .,表示这一格是海水,可以通过;有些位置是 #,表示这一格是礁石,不可以通过;有些位置是 o,表示这一格目前有一艘舰,且舰离开这一格之后,这一格将变为 .

这些 o 表示 Byteasar 的舰队,他们每天可以往上下左右中的一个方向移动一格,但不能有任何一艘舰驶出地图。特别地,Byteasar 对阵形有所研究,所以他不希望在航行的过程中改变阵形,即任何时刻任何两艘舰的相对位置都不能发生变化。

Byteasar 的舰队可以航行无限长的时间,每当一艘舰经过某个格子的时候,这个格子海底的矿藏都将被 Byteasar 获得。请写一个程序,帮助 Byteasar 计算他最多可以获得多少个格子海底的矿藏?

输入输出格式

输入格式

第一行包含两个正整数 n , m n,m n,m,分别表示地图的长和宽。

接下来 n n n 行,每行有 m m m 个字符,每个字符只能是 .#o 中的一个。

输入数据保证至少有一个 o

输出格式

输出一行一个整数,即可以被经过的格子数的最大值。

输入输出样例

输入样例#1:

4 5....#.o#.o.o..o..o..

4 5
....#
.o#.o
.o..o
..o..
输出样例#1:
12
数据范围

img

解题分析

如果我们做过这道题就会感觉到其实这道题是道套路题了… 我们把二位的图压成一维后, 可以通过同样的处理得到左上角合法的位置数量, 进而 B F S BFS BFS得到可行的相对位置。

最后就是算到达过的次数了。 实质上就是在某一个左上角合法的位置可以利用相对位置到达这个点, 也是一次 F F T FFT FFT,最后值 > 1 >1 >1即可确定。

代码如下:

#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cmath>
#include <queue>
#include <cctype>
#include <algorithm>
#define R register
#define IN inline
#define W while
#define gc getchar()
#define MX 705
#define db double
using std::max; using std::min;
const db PI = std::acos(-1.0);
struct Cood {int x, y;}; std::queue <Cood> que;
struct Complex {db im, re;} a[MX * MX * 4], b[MX * MX * 4];
IN Complex operator * (const Complex &x, const Complex &y) {return {x.im * y.re + x.re * y.im, x.re * y.re - x.im * y.im};}
IN Complex operator + (const Complex &x, const Complex &y) {return {x.im + y.im, x.re + y.re};}
IN Complex operator - (const Complex &x, const Complex &y) {return {x.im - y.im, x.re - y.re};}
char mp[MX][MX];
bool vis[MX][MX];
int dir[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
int mxx = 0, mnx = MX, mxy = 0, mny = MX, siz, n, m, tot, lg, delx, dely, ans;
int rev[MX * MX * 4];
void FFT(Complex *dat, const int &typ)
{
	for (R int i = 1; i < tot; ++i) if(rev[i] > i) std::swap(dat[rev[i]], dat[i]);
	R int cur, now, seg, bd, step; Complex buf1, buf2, deal, base;
	for (seg = 1; seg < tot; seg <<= 1)
	{
		base = {std::sin(PI / seg) * typ, std::cos(PI / seg)}; step = seg << 1;
		for (now = 0; now < tot; now += step)
		{
			bd = now + seg; deal = {0, 1};
			for (cur = now; cur < bd; ++cur, deal = deal * base)
			{
				buf1 = dat[cur], buf2 = dat[cur + seg] * deal;
				dat[cur] = buf1 + buf2, dat[cur + seg] = buf1 - buf2;
			}
		}
	}
}
void BFS(R int x, R int y)
{
	Cood now;
	que.push({x, y}); vis[x][y] = false;
	W (!que.empty())
	{
		now = que.front(); que.pop(); x = now.x, y = now.y;
		a[(x - 1) * m + y - 1] = {0, 1};
		for (R int i = 0; i < 4; ++i)
		if(vis[x + dir[i][0]][y + dir[i][1]])
		que.push({x + dir[i][0], y + dir[i][1]}), vis[x + dir[i][0]][y + dir[i][1]] = false;
	}
}
int main(void)
{
	scanf("%d%d", &n, &m); siz = n * m;
	for (R int i = 1; i <= n; ++i) scanf("%s", mp[i] + 1);
	for (R int i = 1; i <= n; ++i)
	for (R int j = 1; j <= m; ++j)
	if(mp[i][j] == 'o') 
	{
		mxx = max(mxx, j); mnx = min(mnx, j);
		mxy = max(mxy, i); mny = min(mny, i);
	}
	else if(mp[i][j] == '#') a[siz - (i - 1) * m - j] = {0, 1};
	delx = mxx - mnx, dely = mxy - mny;
	for (R int i = 1; i <= n; ++i) for (R int j = 1; j <= m; ++j) if(mp[i][j] == 'o')
	b[(i - mny) * m + j - mnx] = {0, 1};
	for (tot = 1; tot <= n * m * 2; tot <<= 1, ++lg);
	for (R int i = 1; i < tot; ++i) rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << (lg - 1));
	FFT(a, 1), FFT(b, 1);
	for (R int i = 0; i < tot; ++i) a[i] = a[i] * b[i];
	FFT(a, -1);
	int bdx = m - delx, bdy = n - dely;
	for (R int i = 1; i <= bdy; ++i)  for (R int j = 1; j <= bdx; ++j)
	if(fabs(a[siz - (i - 1) * m - j].re) < 0.5) vis[i][j] = true;
	for (R int i = 0; i < tot; ++i) a[i] = {0, 0};
	BFS(mny, mnx);
	FFT(a, 1);
	for (R int i = 0; i < tot; ++i) a[i] = a[i] * b[i];
	FFT(a, -1);
	for (R int i = 0; i < siz; ++i) if(a[i].re > 0.5) ++ans;
	printf("%d", ans);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值