【BZOJ】5217: [Lydsy2017省队十连测]航海舰队 -myyFFT

传送门:bzoj5217


题解

为方便表示,首先设下标从 0 0 0开始,坐标范围 0 ≤ x &lt; n , 0 ≤ y &lt; m 0\leq x&lt;n,0\leq y&lt;m 0x<n,0y<m

可以把环境图(只考虑海水和礁石)一行一行接起来转换成一个长度为 n × m n\times m n×m 01 01 01 S S S(若下标从 0 0 0开始,则 S i × m + j = 1 S_{i\times m+j}=1 Si×m+j=1表示位置 ( i , j ) (i,j) (i,j)上有礁石。

同样把包围舰队的最小矩形抠出来,保留最上一行到右下角的位置的状态转换成 01 01 01 T T T(设矩形上下边界分别为 x 1 , x 2 x_1,x_2 x1,x2,左右边界分别为 y 1 , y 2 y_1,y_2 y1,y2,则表示从 ( x 1 , 0 ) (x_1,0) (x1,0) ( x 2 , y 2 ) (x_2,y_2) (x2,y2)的所有点的状态,串长 ( x 2 − x 1 ) × m + y 2 − y 1 + 1 (x_2-x_1)\times m+y_2-y_1+1 (x2x1)×m+y2y1+1 T ( i − x 0 ) × m + j − y 0 = 1 T_{(i-x_0)\times m+j-y_0}=1 T(ix0)×m+jy0=1表示位置 ( i , j ) (i,j) (i,j)上有舰。

考虑到当且仅当 S S S中长度为 ∣ T ∣ |T| T的串(设右下角的点为 e d ed ed)不存在 S e d − i = T ∣ T ∣ − i = 1 S_{ed-i}=T_{|T|-i}=1 Sedi=TTi=1的情况时,舰队右下角可以处于 e d ed ed(能否到达可以 b f s bfs bfs求出,这里先求出能否处于的位置),那么将 T T T串翻转,变成 S e d − i = T i = 1 S_{ed-i}=T_i=1 Sedi=Ti=1就成了卷积形式,做一遍 F F T FFT FFT即可。

卷积过后,若某一点位置上为 0 0 0,则表示可以处于,从舰队初始右下角的点开始 b f s bfs bfs,得到所有可以到达的点,转换成 01 01 01 A A A( A i × m + j = 1 A_{i\times m+j}=1 Ai×m+j=1表示舰队可以处于位置 ( i , j ) (i,j) (i,j))。

每个点第一次被到达才产生贡献,所以会算重。考虑一个点 p = e d − i p=ed-i p=edi,若存在 A p + i = 1 A_{p+i}=1 Ap+i=1,所以同样转成了卷积形式 A p + i = T e d − i = 1 A_{p+i}=T_{ed-i}=1 Ap+i=Tedi=1。再 F F T FFT FFT后统计系数大于 0 0 0的个数即可(实际上对应的是位数 − ( ∣ T ∣ − 1 ) -(|T|-1) (T1)上的点是否被到达)。

用myyfft似乎要快一些myyfft戳这里


代码

#include<bits/stdc++.h>
#define RI register
using namespace std;
typedef double db;
const int N=2e6+10,M=702;
const int MX=M*M;
const db pi=acos(-1.0);

int ans,n,m,all,are,len,L,rv[N],t[MX];
int xx[2],yy[2],H,W,g[MX],vs[MX];
char s[M][M];

struct P{
  int x,y;
  P(){};
  P(int x_,int y_){x=x_;y=y_;} 
}temp;

queue<P>que;

struct cc{
	db r,i;
	cc(){r=i=0;};
	cc(db r_,db i_){r=r_;i=i_;}
	cc operator +(const cc&ky){return cc(r+ky.r,i+ky.i);}
	cc operator -(const cc&ky){return cc(r-ky.r,i-ky.i);}
	cc operator *(const cc&ky){return cc(r*ky.r-i*ky.i,r*ky.i+i*ky.r);}
	cc operator /(const int&ky){return cc(r/(db)ky,i/(db)ky);}
	inline cc conj(){return cc(r,-i);}
}a[N],b[N];

inline void FFT(cc *e,int ptr)
{
	RI int i,j,k,t;cc bs,ori,ix,iy;
	for(i=1;i<len;++i) if(i<rv[i]) swap(e[i],e[rv[i]]);
	for(i=1;i<len;i<<=1){
		ori=cc(cos(pi/i),ptr*sin(pi/i));
		for(j=0;j<len;j+=(i<<1)){
			bs=cc(1,0);
			for(k=0;k<i;++k,bs=bs*ori){
				ix=e[j+k];iy=e[i+j+k]*bs;
				e[j+k]=ix+iy;e[i+j+k]=ix-iy;
			}
		}
	}
	if(ptr==1) return; 
	for(i=0;i<len;++i) e[i]=e[i]/len;
}

inline void init()
{
	RI int i,j,pos=0;
	scanf("%d%d",&n,&m);
	all=n*m;
    xx[0]=n+1;yy[0]=m+1;
	for(i=0;i<n;++i){
		scanf("%s",s[i]);
		for(j=0;j<m;++j){
		    if(s[i][j]=='#') a[pos].r=1;
		    else if(s[i][j]=='o'){
		    	xx[0]=min(i,xx[0]);
		    	xx[1]=max(i,xx[1]); 
		    	yy[0]=min(j,yy[0]);
		    	yy[1]=max(j,yy[1]);
		    }
		    pos++;
		}
	}
	H=xx[1]-xx[0]+1;W=yy[1]-yy[0]+1;
	pos=0;are=(H-1)*m+W;
	for(i=xx[0];i<=xx[1];++i,pos+=m)
		for(j=yy[0];j<=yy[1];++j)
		 if(s[i][j]=='o')
		    a[are-1-pos-j+yy[0]].i=1,
			t[pos+j-yy[0]]=1; 
}

inline void ins(P p)
{
	int rc=p.x*m+p.y;
    if(!g[rc] || vs[rc]) return;
    vs[rc]=1;
	if(p.x>0) que.push(P(p.x-1,p.y));
	if(p.x<n-1) que.push(P(p.x+1,p.y));
	if(p.y>0) que.push(P(p.x,p.y-1));
	if(p.y<m-1) que.push(P(p.x,p.y+1)); 
}

inline void work()
{
	RI int i,j;
	for(len=1,L=0;len<all+are;len<<=1) L++;
	for(i=1;i<len;++i) rv[i]=(rv[i>>1]>>1)|((i&1)<<(L-1));
	FFT(a,1);
	for(i=0;i<len;++i){
		j=(len-i)&(len-1);
		b[i]=(a[i]*a[i]-(a[j]*a[j]).conj())*cc(0,-0.25);
	}
	FFT(b,-1);
	for(i=H-1;i<n;++i) 
	 for(j=W-1;j<m;++j) 
	   if(b[i*m+j].r<0.5) g[i*m+j]=1;
	que.push(P(xx[1],yy[1]));
	for(;!que.empty();){
		temp=que.front();que.pop();
		ins(temp);
	}
	for(i=0;i<len;++i) a[i]=cc(0,0);
	for(i=0;i<all;++i) if(vs[i]) a[i].r=1;
	for(i=0;i<are;++i) if(t[i]) a[i].i=1;
	FFT(a,1);
	for(i=0;i<len;++i){
		j=(len-i)&(len-1);
		b[i]=(a[i]*a[i]-(a[j]*a[j]).conj())*cc(0,-0.25);
	}
	FFT(b,-1);
	for(i=0;i<len;++i) if(b[i].r>0.5) ans++;
	printf("%d\n",ans);
}

int main(){
	init();
	work();
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值