【FFT-类字符串匹配】BZOJ5217 [Lydsy2017省队十连测]航海舰队

【题目】
BZOJ
一个 n × m n\times m n×m的网格图,其中".“表示海水,”#"表示障碍,"o"表示舰队,舰队可以四方向移动,要求舰队内部的相对位置不变,问舰队最多能经过多少个格子。
n , m ≤ 700 n,m\leq 700 n,m700

【解题思路】
根据 THUPC \text{THUPC} THUPC的赛艇这题,不难想到可以用 FFT \text{FFT} FFT进行匹配。
那么二维转一维以后是个矩形匹配问题,将舰队的位置和障碍的位置设成 1 1 1然后反过来一个,结果为 0 0 0的位置就是舰队矩形可行的左上角(或者你自己搞的其他什么特征)。
最后再 DFS \text{DFS} DFS一遍可以得到答案。

【参考代码】

#include<bits/stdc++.h>
#define fi first
#define se second
#define mkp make_pair
#define id(x,y) (x*m+y)
using namespace std;

typedef pair<int,int> pii;
typedef double db;
const int N=705,M=N*N*4;
const int dx[]={1,0,-1,0},dy[]={0,1,0,-1};
const db pi=acos(-1),eps=0.5;

int n,m,xl,yl,xr,yr;

namespace Poly
{
	int L,mm,rev[M];
	struct cd
	{
		db r,i;
		cd(db _r=0,db _i=0):r(_r),i(_i){}
		cd operator +(const cd&rhs)const{return cd(r+rhs.r,i+rhs.i);}
		cd operator -(const cd&rhs)const{return cd(r-rhs.r,i-rhs.i);}
		cd operator *(const cd&rhs)const{return cd(r*rhs.r-i*rhs.i,r*rhs.i+i*rhs.r);}
	}a[M],b[M];
	void reget(int x)
	{
		for(L=0,mm=1;mm<=x;mm<<=1,++L);
		for(int i=0;i<mm;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<(L-1));
	}
	void fft(cd *a,int n,int f)
	{
		for(int i=0;i<n;++i) if(i<rev[i]) swap(a[i],a[rev[i]]);
		for(int i=1;i<n;i<<=1)
		{
			cd wn=cd(cos(pi/i),f*sin(pi/i));
			for(int j=0;j<n;j+=(i<<1))
			{
				cd w=cd(1,0);
				for(int k=0;k<i;++k,w=w*wn)
				{
					cd x=a[j+k],y=w*a[i+j+k];
					a[j+k]=x+y;a[i+j+k]=x-y;
				}
			}
		}
		if(!~f) for(int i=0;i<n;++i) a[i].r/=n;
	} 
}
using namespace Poly;

namespace DreamLolita
{
	int ans,vis[N][N];
	char mp[N][N];
	queue<pii>q;
	void bfs(int x,int y)
	{
		q.push(mkp(x,y));vis[x][y]=0;
		while(!q.empty())
		{
			x=q.front().fi;y=q.front().se;q.pop();
			a[(x-1)*m+y-1]=cd(1,0);
			for(int i=0;i<4;++i)
			{
				int nx=x+dx[i],ny=y+dy[i];
				if(vis[nx][ny]) vis[nx][ny]=0,q.push(mkp(nx,ny));
			}
		}
	}
	void solve()
	{
		scanf("%d%d",&n,&m);xl=yl=N;xr=yr=0;
		for(int i=1;i<=n;++i) 
		{
			scanf("%s",mp[i]+1);
			for(int j=1;j<=m;++j)
			{
				if(mp[i][j]=='o') xl=min(xl,i),yl=min(yl,j),xr=max(xr,i),yr=max(yr,j);
				else if(mp[i][j]=='#') a[n*m-(i-1)*m-j]=cd(1,0);
			}
		}	
		for(int i=1;i<=n;++i) for(int j=1;j<=m;++j)
			if(mp[i][j]=='o') b[(i-xl)*m+j-yl]=cd(1,0);
		reget(n*m);
		fft(a,mm,1);fft(b,mm,1);
		for(int i=0;i<mm;++i) a[i]=a[i]*b[i];
		fft(a,mm,-1);
		for(int i=1;i<=n-(xr-xl);++i) for(int j=1;j<=m-(yr-yl);++j)
			if(a[n*m-(i-1)*m-j].r<eps) vis[i][j]=1;
		for(int i=0;i<mm;++i) a[i]=cd(0,0);
		bfs(xl,yl);
		fft(a,mm,1);
		for(int i=0;i<mm;++i) a[i]=a[i]*b[i];
		fft(a,mm,-1);
		for(int i=0;i<n*m;++i) if(a[i].r>eps) ++ans;
		printf("%d\n",ans);
	}
}

int main()
{
#ifndef ONLINE_JUDGE
	freopen("BZOJ5217.in","r",stdin);
	freopen("BZOJ5217.out","w",stdout);
#endif
	DreamLolita::solve();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值