6840. 【2020.11.5提高组模拟】铲雪(snow)

 Description

Input

第一行三个正整数 n,m,q
接下来 q 行每行一个操作

Output

对于所有 3 操作输出答案

Sample Input

样例输入 1:
4 3 9
3 2 1 3 3 1
3 2 1 3 3 1
2 1
2 3
1 1
3 2 1 3 3 3
3 2 1 3 3 3
2 2
3 2 1 3 3 6

Sample Output

样例输出 1:
3
−1
5
−1
3

Data Constraint

Solution

19%暴力:

O(nmq)。

100%线段树:

考虑到要清理雪就清理一行,所以我们可以想到如果当前某一行的最大值都可以走(小于等于k),那么这一行必定都可以走。

顺着往下想,如何维护时间的变化?

首先对于每次雪的高度增加1,可以使用一个变量记录当前时间,再分别记录每一行,每一列最后一次被清理的时间,清理一次就更新一行或一列。

那么这个点的高度记为   Time-max(h[i],g[j])

接下来我们考虑如何计算两点之间最短路。

因为必定走直线,那么大致分为以下3种情况:

  1. 两点同行或同列,能走就直接走
  2. 两点不同行且不同列,通过走其中一个点的行和另一个点的列
  3. 两点不同行不同列,通过走两个点的列和任意一行,或两点的行和任意一列。

 

对于第1种情况,可以直接判断满足以下两个条件之一即可:

  1. 两点之间的所有行都可以走
  2. 两点之间的所有列都可以走

对于第3中情况,因为要求最小值,所有要分类讨论一下:

1.两个点的两列都可以走,则需要查找可以走的一行,可以分成3种情况

  1. (两点之中)较小的行之前的最大的一个能走的行 ,答案需计算
  2. (两点之中)较小行数和较大行数之间任意一个能走的行,答案即为曼哈顿距离
  3. (两点之中)较大的行之后的最小的一个能走的行,答案需计算

但是可以发现其实这三种可以合并成2种情况:

  1. 较大行之前的最大的一个能走的行,答案需计算
  2. 较小行之后的最小的一个能走的行,答案需计算

2.两个点的两行都可以走,则需要查找可以走的一列,同样可以分成2种情况

 

  1. 较大列之前的最大的一个能走的列,答案需计算
  2. 较小列之后的最小的一个能走的列,答案需计算

3.最后再把两种情况取min

 

那么问题就是如何判断

1.一行能否走,一列能否走

2.两行之间是否都能走,两列之间是否都能走

3.两行之间是否存在可以走的列(若存在则返回最前面或最后面的列数),两列之间是否存在可以走的行(若存在则返回最前面或最后面的行数)。

那么这样很容易想到用线段树去维护,用行和列分别维护。

每次加入一行或一列,将时间加入到行或列的线段树中。

对于第1个问题:判断 T- 那一行或列的时间是否<=k,

对于第2个问题:判断 T- 两行之间时间的最小值是否<=k

对于第3个问题:查找线段树中满足 T-行/列的时间 <=k的最左/右边的一行/列,即查找 线段树中 时间>=T-k 的前驱或后继即可。

时间复杂度O(q ~log~n)

Code 

#include<cstdio>
#include<cstring>
#include<cmath>
#define I int
#define ll long long
#define ls x<<1
#define rs ls|1
#define LL ls,l,M
#define RR rs,M+1,r
#define P(x) {ans=x;return;}
#define N 1000004
using namespace std;
I op,n,m,q,x,y,xx,yy,k,T;
I ans,xM,xm,yM,ym,a[N],b[N];
struct seg{I mx,mn;}f[N*4],g[N*4];
void R(I &x){
	x=0;I w=1;char c=getchar();
	while(c<'0'||c>'9'){if(c=='-') w=-1;c=getchar();}
	while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
	x*=w;
}
inline I max(I x,I y){return x>y?x:y;}
inline I min(I x,I y){return x<y?x:y;}
inline void ins(I k,I v,I x=1,I l=1,I r=(op==1)?n:m){
	if(l==r){(op==1)?f[x]=seg{a[l]=v,v}:g[x]=seg{b[l]=v,v};return;}
	I M=l+r>>1;
	(k<=M)?ins(k,v,LL):ins(k,v,RR);
	(op==1)?f[x]=seg{max(f[ls].mx,f[rs].mx),min(f[ls].mn,f[rs].mn)}
	:g[x]=seg{max(g[ls].mx,g[rs].mx),min(g[ls].mn,g[rs].mn)};
}
inline I fnd(I l2,I r2,I ex,I x=1,I l=1,I r=(op==1)?n:m){
	if(l>r2||r<l2) return (!ex)?N:0;
	if(l>=l2&&r<=r2) return (!ex)?((op==1)?f[x].mn:g[x].mn):((op==1)?f[x].mx:g[x].mx);
	I M=l+r>>1,vl=fnd(l2,r2,ex,LL),vr=fnd(l2,r2,ex,RR);
	return (!ex)?min(vl,vr):max(vl,vr);
}
inline I sek(I l2,I r2,I val,I opt,I x=1,I l=1,I r=(op==1)?n:m){
	if((op==1&&f[x].mx<val)||(op==2&&g[x].mx<val)||l>r2||r<l2) return 0;
	if(l==r) return l;
	I M=l+r>>1,t;
	return (!opt)?(t=sek(l2,r2,val,opt,LL))?t:sek(l2,r2,val,opt,RR)
	:(t=sek(l2,r2,val,opt,RR))?t:sek(l2,r2,val,opt,LL);
}
void work(){
	R(x),R(y),R(xx),R(yy),R(k);
	xM=max(x,xx),xm=x+xx-xM,yM=max(y,yy),ym=y+yy-yM;
	I len=yM-ym+xM-xm,s1,s2;
	I Tx=T-a[x],Txx=T-a[xx],Ty=T-b[y],Tyy=T-b[yy];
	if((op=1,T-fnd(xm,xM,0)<=k)||(op=2,T-fnd(ym,yM,0)<=k)||Tx<=k&&Tyy<=k||Ty<=k&&Txx<=k) P(len);
	if(op=1,Ty<=k&&Tyy<=k){
		s1=(s1=sek(1,xM,T-k,1))?yM-ym+abs(x-s1)+abs(xx-s1):0;
		s2=(s2=sek(xm,n,T-k,0))?yM-ym+abs(x-s2)+abs(xx-s2):0;
		if(s1&&s2) P(min(s1,s2));if(s1||s2) P(s1+s2);
	}
	if(op=2,Tx<=k&&Txx<=k){
		s1=(s1=sek(1,yM,T-k,1))?xM-xm+abs(y-s1)+abs(yy-s1):0;
		s2=(s2=sek(ym,m,T-k,0))?xM-xm+abs(y-s2)+abs(yy-s2):0;
		if(s1&&s2) P(min(s1,s2));if(s1||s2) P(s1+s2);
	}
	P(-1);
}
I main(){
	freopen("snow.in","r",stdin);
	freopen("snow.out","w",stdout);
	R(n),R(m),R(q);
	for(T=1;T<=q;T++){
		R(op);
		if(op!=3){R(x);ins(x,T);}else{work();printf("%d\n",ans);}
	}
	return 0;
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值