BZOJ5217[Lydsy2017省队十连测] 航海舰队

14 篇文章 0 订阅
4 篇文章 0 订阅

原题链接:https://www.lydsy.com/JudgeOnline/problem.php?id=5217

航海舰队

Description

Byteasar 组建了一支舰队!他们现在正在海洋上航行着。海洋可以抽象成一张n×m 的网格图,其中有些位置是“.”,表示这一格是海水,可以通过;有些位置是“#”,表示这一格是礁石,不可以通过;有些位置是“o”,表示这一格目前有一艘舰,且舰离开这一格之后,这一格将变为“.”。这些“o” 表示Byteasar 的舰队,他们每天可以往上下左右中的一个方向移动一格,但不能有任何一艘舰驶出地图。特别地,Byteasar 对阵形有所研究,所以他不希望在航行的过程中改变阵形,即任何时刻任何两艘舰的相对位置都不能发生变化。Byteasar 的舰队可以航行无限长的时间,每当一艘舰经过某个格子的时候,这个格子海底的矿藏都将被Byteasar 获得。请写一个程序,帮助Byteasar 计算他最多可以获得多少个格子海底的矿藏?

Input

第一行包含两个正整数n;m,分别表示地图的长和宽。n, m <= 700
接下来n 行,每行有m 个字符,每个字符只能是“.”、“#”、“o” 中的一个。
输入数据保证至少有一个“o”。

Output

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

Sample Input

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

Sample Output

12

题解

首先我们可以将整张地图各行首尾相接的扯成一维,对于舰队也可以采取同样的操作,找出包含舰队的最小矩形,以该矩形左上角为起点,右下角为终点将舰队从一维的地图里扯出来,样例构造出的两个串如下:

map: ....#..#............

fleet: o..o.o..o..o..

这样一个在二维图中的合法舰队位置就能表示成一个一维的合法字符串匹配了,其中 '.'为通配符。等等,通配符匹配?我可能在哪儿看到过,在BZOJ4503 两个串中,我们构造了一个函数 f ( x ) = ∑ i = 0 m − 1 ( S x + i − T i ) 2 × T i f(x)=\sum_{i=0}^{m-1}(S_{x+i}-T_i)^2\times T_i f(x)=i=0m1(Sx+iTi)2×Ti,在这道题里,由于两个串都有通配符且除了通配符外每个串只有一种字符,且这一种字符都是独有的,无法与另一个串匹配,所以我们的函数就可以定义的简单粗暴一点: f ( x ) = ∑ i = 0 m − 1 S x + i × T i f(x)=\sum_{i=0}^{m-1}S_{x+i}\times T_i f(x)=i=0m1Sx+i×Ti

然后我们就可以愉快的 F F T \mathcal{FFT} FFT找到地图串那些地方可以作为开头放置舰队串,但是这些找到的位置并不完全合法,首先舰队不能移出海域,所以我们只能统计可以放下舰队的地方,其次有些地方可能是被礁石封死了的,需要从初始位置所在的矩形坐上角开始 d f s dfs dfs将能相互到达的位置标出来,只有这些位置才是合法的。

最后,将合法位置的一维映射设为 1 1 1,其余位置设为 0 0 0,将舰队串有船的地方设为 1 1 1,其余位置为 0 0 0,做一次 F F T \mathcal{FFT} FFT,乘出来的串里不为 0 0 0的地方就是可以到达的,计数,输出, A C \mathcal{AC} AC

代码
#include<bits/stdc++.h>
using namespace std;
const int M=(1<<20)+5,N=705;
const double pi=acos(-1.0);
struct cpx{double x,y;}mmp[M],fle[M],shi[M];
cpx operator+(cpx a,cpx b){return (cpx){a.x+b.x,a.y+b.y};}
cpx operator-(cpx a,cpx b){return (cpx){a.x-b.x,a.y-b.y};}
cpx operator*(cpx a,cpx b){return (cpx){a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x};}
int asc[300],rev[M],ans[M],sq[N][N],n,m,s1=1e9,s2=1e9,t1,t2,tot=-1,mx,len;
int dx[]={0,0,1,-1};
int dy[]={1,-1,0,0};
char ch[705];
void dfs(int x,int y){sq[x][y]=0;mmp[(x-1)*m+y-1].x=1;for(int i=0;i<4;++i)if(sq[x+dx[i]][y+dy[i]])dfs(x+dx[i],y+dy[i]);}
void fft(cpx *f,int typ)
{
	cpx wn,w,x,y;int i,j,k,mid;
	for(i=0;i<mx;++i)if(i<rev[i])swap(f[i],f[rev[i]]);
	for(mid=1;mid<mx;mid<<=1)for(j=0,wn=(cpx){cos(pi/mid),typ*sin(pi/mid)};j<mx;j+=mid<<1)
	for(k=0,w=(cpx){1,0};k<mid;++k,w=w*wn)x=f[j+k],y=w*f[j+mid+k],f[j+k]=x+y,f[j+mid+k]=x-y;
}
void in()
{
	asc['.']=0,asc['o']=1,asc['#']=2;
	scanf("%d%d",&n,&m);
	for(int i=0,j;i<n;++i)for(scanf("%s",ch),j=0;j<m;++j)
	if((mmp[i*m+j].x=asc[ch[j]])==1)s1=min(s1,i),s2=min(s2,j),t1=max(t1,i),t2=max(t2,j);
}
void ac()
{
	int i,j;
	for(i=s1*m+s2;i<=t1*m+t2;++i)fle[++tot].x=(mmp[i].x==2?0:mmp[i].x),shi[tot].x=bool(fle[tot].x);
	for(i=n*m;i>=0;--i)if(mmp[i].x==1)mmp[i].x=0;
	for(mx=1;mx<=n*m+tot;mx<<=1,++len);for(i=0;i<mx;++i)rev[i]=rev[i>>1]>>1|((i&1)<<(len-1));
	for(reverse(fle,fle+tot+1),fft(fle,1),fft(mmp,1),i=0;i<mx;++i)mmp[i]=fle[i]*mmp[i];
	for(fft(mmp,-1),i=0;i<n-(t1-s1);++i)for(j=0;j<m-(t2-s2);++j)if(int(mmp[tot+i*m+j].x/mx+0.5)==0)sq[i+1][j+1]=1;
	for(i=0;i<mx;++i)mmp[i]=(cpx){0,0};
	for(dfs(s1+1,s2+1),fft(mmp,1),fft(shi,1),i=0;i<mx;++i)mmp[i]=mmp[i]*shi[i];
	for(fft(mmp,-1),i=tot=0;i<=n*m;++i)if(int(mmp[i].x/mx+0.5))++tot;
	printf("%d",tot);
}
int main(){in();ac();}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShadyPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值