洛谷 P3438 - [POI2006]ZAB-Frogs(乱搞/李超线段树)

题面传送门

首先一眼二分答案,我们假设距离 \((i,j)\) 最近的 scarefrog 离它的距离为 \(mn_{i,j}\),那么当我们二分到 \(mid\) 时我们显然只能经过 \(mn_{i,j}\ge mid\) 的点,如果我们已经知道了 \(mn_{i,j}\),那么检验 \(mid\) 是否可行就是一遍 BFS 能搞定的事,于是我们的目标就是求出 \(mn_{i,j}\) 即可。

故下面就有两种做法,一种是我的乱搞做法,一种是正经的做法。


乱搞做法

这就是某个奇怪的人自己发明出来的某个奇怪的基于某个奇怪的假做法的乱搞做法(

显然对于某个点 \((x,y)\) 而言,对它产生贡献的点可能在它的左上方、右上方、左下方、右下方(对于与 \((x,y)\) 在同一行或同一列的点,我们显然可以把它简单归约到 \((x,y)\) 的某一侧),显然四种情况是类似的,我们只需解决一种情况即可求出解决另外几种。考虑怎样求出左上角的答案,那么对于两个 scarefrog 所在的点 \((x_1,y_1),(x_2,y_2)\),如果 \(x_1<x_2,y_1<y_2\)\((x_1,y_1),(x_2,y_2)\) 都在 \((x,y)\) 的左上角,那 \((x_1,y_1)\) 显然是不优的,因此我们维护一个队列满足随着 \(x\) 的增大,\(y\) 单调递减,表示决策点的集合。其次我们还可以发现,对于一个点 \(P(x,y)\) 和两个决策点 \(A(x_1,y_1),B(x_2,y_2)\),如果 \(PA>PB\),且 \(x_1>x_2\),那么如果我们将 \(P\) 再往右多移动几格,到 \(P(x,y+k)\),那么感性理解一下,\(PA\) 增加的长度 \(\Delta PA\) 大于 \(PB\) 增加的长度 \(\Delta PB\),因此还有 \(PA>PB\),此时 \(A\) 点就是没有用的了,因此我们考虑维护一个单调队列,随着 \(x\) 的增大,\(y\) 在不断减小,同时该点到点 \(P\) 的距离也在不断减小,那么我们只需取出队首的元素更新答案即可。

这个做法看起来非常正确,但非常抱歉,它是有漏洞的,因为当你将 \(P\)\((x,y)\) 移至 \((x,y+1)\) 时,队列中的元素并不一定还满足到 \(P\) 点的距离单调。

不过这个做法在随机数据下表现非常优秀,它奇迹般地只 WA 了 \(3\) 个点,取得了 \(88\) 分的好成绩,于是我就灵机一动,将“取对首元素更新答案”改为“取队列前 \(30\) 个元素更新答案”,然后……它!过!了!

时间复杂度 \(\mathcal O(30nm+nm\log nm)\)

感觉这个做法非常不靠谱,欢迎鸽鸽们来叉

const int MAXN=1000;
const int dx[]={1,0,-1,0};
const int dy[]={0,1,0,-1};
int dis(int x1,int y1,int x2,int y2){
	return (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
}
int n,wx,wy,sx,sy,ex,ey;
bool is[MAXN+5][MAXN+5];
int pos[MAXN+5],mn[MAXN+5][MAXN+5];
bool vis[MAXN+5][MAXN+5];
bool check(int lim){
	if(mn[sx][sy]<lim||mn[ex][ey]<lim) return 0;
	memset(vis,0,sizeof(vis));queue<pii> q;
	q.push(mp(sx,sy));vis[sx][sy]=1;
	while(!q.empty()){
		pii p=q.front();q.pop();int x=p.fi,y=p.se;
		for(int d=0;d<4;d++){
			int nx=x+dx[d],ny=y+dy[d];
			if(nx<1||nx>wx||ny<1||ny>wy) continue;
			if(mn[nx][ny]<lim||vis[nx][ny]) continue;
			vis[nx][ny]=1;q.push(mp(nx,ny));
		}
	} return vis[ex][ey];
}
int main(){
	scanf("%d%d%d%d%d%d%d",&wx,&wy,&sx,&sy,&ex,&ey,&n);
	for(int i=1,x,y;i<=n;i++) scanf("%d%d",&x,&y),is[x][y]=1;
	memset(mn,63,sizeof(mn));
	memset(pos,0,sizeof(pos));
	for(int i=1;i<=wx;i++){
		static pii q[MAXN+5];int hd=1,tl=0;
		for(int j=1;j<=wy;j++){
			if(is[i][j]) pos[j]=i;
			if(pos[j]){
				while(hd<=tl&&dis(i,j,pos[j],j)<=dis(i,j,q[tl].fi,q[tl].se)) --tl;
				q[++tl]=mp(pos[j],j);
			} while(hd<tl&&dis(i,j,q[hd].fi,q[hd].se)>=dis(i,j,q[hd+1].fi,q[hd+1].se)) ++hd;
			for(int l=hd;l<=min(hd+30,tl);l++)
				chkmin(mn[i][j],dis(i,j,q[l].fi,q[l].se));
		}
	}
	memset(pos,0,sizeof(pos));
	for(int i=1;i<=wx;i++){
		static pii q[MAXN+5];int hd=1,tl=0;
		for(int j=wy;j;j--){
			if(is[i][j]) pos[j]=i;
			if(pos[j]){
				while(hd<=tl&&dis(i,j,pos[j],j)<=dis(i,j,q[tl].fi,q[tl].se)) --tl;
				q[++tl]=mp(pos[j],j);
			} while(hd<tl&&dis(i,j,q[hd].fi,q[hd].se)>=dis(i,j,q[hd+1].fi,q[hd+1].se)) ++hd;
			for(int l=hd;l<=min(hd+30,tl);l++)
				chkmin(mn[i][j],dis(i,j,q[l].fi,q[l].se));
		}
	}
	memset(pos,0,sizeof(pos));
	for(int i=wx;i;i--){
		static pii q[MAXN+5];int hd=1,tl=0;
		for(int j=1;j<=wy;j++){
			if(is[i][j]) pos[j]=i;
			if(pos[j]){
				while(hd<=tl&&dis(i,j,pos[j],j)<=dis(i,j,q[tl].fi,q[tl].se)) --tl;
				q[++tl]=mp(pos[j],j);
			} while(hd<tl&&dis(i,j,q[hd].fi,q[hd].se)>=dis(i,j,q[hd+1].fi,q[hd+1].se)) ++hd;
			for(int l=hd;l<=min(hd+30,tl);l++)
				chkmin(mn[i][j],dis(i,j,q[l].fi,q[l].se));
		}
	}
	memset(pos,0,sizeof(pos));
	for(int i=wx;i;i--){
		static pii q[MAXN+5];int hd=1,tl=0;
		for(int j=wy;j;j--){
			if(is[i][j]) pos[j]=i;
			if(pos[j]){
				while(hd<=tl&&dis(i,j,pos[j],j)<=dis(i,j,q[tl].fi,q[tl].se)) --tl;
				q[++tl]=mp(pos[j],j);
			} while(hd<tl&&dis(i,j,q[hd].fi,q[hd].se)>=dis(i,j,q[hd+1].fi,q[hd+1].se)) ++hd;
			for(int l=hd;l<=min(hd+30,tl);l++)
				chkmin(mn[i][j],dis(i,j,q[l].fi,q[l].se));
		}
	} int l=0,r=2e6,p=0;
//	for(int i=1;i<=wx;i++) for(int j=1;j<=wy;j++) printf("%d%c",mn[i][j]," \n"[j==wy]);
	while(l<=r){
		int mid=l+r>>1;
		if(check(mid)) p=mid,l=mid+1;
		else r=mid-1;
	} printf("%d\n",p);
	return 0;
}

正经做法

还是考虑怎样求 \(mn_{i,j}\),我们记 \(f_{i,j}\) 表示与 \((i,j)\) 在同一列的点中,与 \((i,j)\) 距离最近的坏点的距离——那么显然 \(f_{i,j}\) 可以通过扫一遍整个 matrix 求出,时间复杂度 \(nm\)

接下来考虑知道了 \(f_{i,j}\) 怎样求 \(mn\),那么我们就枚举离 \((i,j)\) 最近的坏点所在的列 \(k\),可得转移方程 \(mn_{i,j}=\min\limits_{k}\{f_{i,k}^2+(j-k)^2\}\),稍微变形一下即可得到 \(mn_{i,j}=j^2+\min\limits_{k}\{-2jk+f_{i,k}^2+k^2\}\),这显然就是一个个一次函数,如果我们记 \(l_k:y=-2kx+f_{i,k}^2+k^2\),那么求 \(mn_{i,j}\) 只需求出 \(\max\{l_k(j)\}\),可以用凸包/斜率优化/李超线段树维护,本人写的是李超线段树的求法,因为不容易爆精度

const int MAXN=1000;
const int INF=0x3f3f3f3f;
const int dx[]={1,0,-1,0};
const int dy[]={0,1,0,-1};
int wx,wy,sx,sy,ex,ey,n;
bool is[MAXN+5][MAXN+5];
int mn[MAXN+5][MAXN+5],f[MAXN+5][MAXN+5];
struct line{
	int k,b;
	line(int _k=0,int _b=INF):k(_k),b(_b){}
	int get(int x){return x*k+b;}
};
struct node{int l,r;line v;} s[MAXN*4+5];
void build(int k,int l,int r){
	s[k].l=l;s[k].r=r;s[k].v=line();if(l==r) return;
	int mid=l+r>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
}
void modify(int k,line v){
	int l=s[k].l,r=s[k].r,mid=l+r>>1;
	int l1=s[k].v.get(l),r1=s[k].v.get(r),m1=s[k].v.get(mid);
	int l2=v.get(l),r2=v.get(r),m2=v.get(mid);
	if(l2>=l1&&r2>=r1) return;
	if(l1>=l2&&r1>=r2) return s[k].v=v,void();
	if(m2<=m1){
		if(l2<=l1) modify(k<<1|1,s[k].v),s[k].v=v;
		else modify(k<<1,s[k].v),s[k].v=v;
	} else {
		if(l2<=l1) modify(k<<1,v);
		else modify(k<<1|1,v);
	}
}
int query(int k,int x){
	if(s[k].l==s[k].r) return s[k].v.get(x);int mid=s[k].l+s[k].r>>1;
	return min(((x<=mid)?query(k<<1,x):query(k<<1|1,x)),s[k].v.get(x));
}
bool vis[MAXN+5][MAXN+5];
bool check(int lim){
	if(mn[sx][sy]<lim||mn[ex][ey]<lim) return 0;
	memset(vis,0,sizeof(vis));queue<pii> q;
	q.push(mp(sx,sy));vis[sx][sy]=1;
	while(!q.empty()){
		pii p=q.front();q.pop();int x=p.fi,y=p.se;
		for(int d=0;d<4;d++){
			int nx=x+dx[d],ny=y+dy[d];
			if(nx<1||nx>wx||ny<1||ny>wy) continue;
			if(mn[nx][ny]<lim||vis[nx][ny]) continue;
			vis[nx][ny]=1;q.push(mp(nx,ny));
		}
	} return vis[ex][ey];
}
int main(){
	scanf("%d%d%d%d%d%d%d",&wx,&wy,&sx,&sy,&ex,&ey,&n);
	for(int i=1,x,y;i<=n;i++) scanf("%d%d",&x,&y),is[x][y]=1;
	memset(f,63,sizeof(f));
	for(int j=1;j<=wy;j++){
		int pre=-INF;
		for(int i=1;i<=wx;i++){
			if(is[i][j]) pre=i;
			chkmin(f[i][j],i-pre);
		} pre=INF;
		for(int i=wx;i;i--){
			if(is[i][j]) pre=i;
			chkmin(f[i][j],pre-i);
		}
	}
	for(int i=1;i<=wx;i++){
		build(1,1,wy);
		for(int j=1;j<=wy;j++) if(f[i][j]<1e9)
			modify(1,line(-2*j,j*j+f[i][j]*f[i][j]));
		for(int j=1;j<=wy;j++) mn[i][j]=query(1,j)+j*j;
	} int l=0,r=2e6,p=0;
//	for(int i=1;i<=wx;i++) for(int j=1;j<=wy;j++) printf("%d%c",mn[i][j]," \n"[j==wy]);
	while(l<=r){
		int mid=l+r>>1;
		if(check(mid)) p=mid,l=mid+1;
		else r=mid-1;
	} printf("%d\n",p);
	return 0;
}
posted @ 2021-07-02 09:35  tzc_wk  阅读(58)  评论(0编辑  收藏  举报