【51Nod1463 找朋友】线段树+简单离线

给定:
两个长度为n的数列A 、B
一个有m个元素的集合K
询问Q次
每次询问[l,r],输出区间内满足|Bi-Bj|∈K 的最大Ai+Aj
数据约定:
n,Q<=100000
m <= 10
0<=A[i]<=1000000000
1<=B[i]<=n
1<=K[i]<=n
保证B[i]互不相等


线段树萌新看到这题就懵了……果然还是不行啊quq,然后就上网找到了题解和dalao的代码,认真参悟一番,终于A啦~


思路

题目说是很多询问,问最大值,看起来就很线段树。
要写线段树的话,我们就要想好,究竟是用线段树维护什么呢。
在这道题里,应该是维护当前位置pos的最大值
这样的话,查询就只需要查询询问中l-r的最大值就阔以啦。
但还是有一点点问题~
这个最大值是会被各种东西影响的,比如bi的另一半在不在询问区间啦之类的。
所以我们需要离线!
将所有询问区间按右端点排序,然后逐个询问地加入每个点。每次都从上一次的r加到这一次的r。假设这个从r到r的循环的变量是j吧~
因为k的个数比较少,可以遍历每一个k,在保证bj满足区间要求的情况下遍历每个k,求出满足要求的另一个b。
因为j一定是满足要求的(j在右区间内,而右区间是不断扩张的,前边满足要求的不会影响后边的),这样判断某个点的最大值可行与否的时候,只需要判断它左边的部分是否满足要求就可以啦。
所以维护的当前位置pos的最大值,其实也就是左端点(bi)为pos的最大值 m a x ( a i + a j ) max(a_i+a_j) maxai+aj啦~


我错过的一些细节

变量名称循环范围打错什么的就不提了。。。
那好像也没什么了。。
哦对了,这题的update和正常的不太一样。。
这棵线段树的update是将pos及它的列祖列宗的最大值都更新了,感觉是不可以用lazytag呢。(话说只更新 l o g n logn logn个结点还lazy的话也太lazy了。。)(好像是因为是单点更新?)
不可以,而且莫得必要。
糟糕的配图


代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 100010;
ll n,m,w[N],a[N],b[N],bpos[N],K[15],ans[N<<2],tag[N<<2],Q,tans[N],re[N],res;
struct node{
	ll l,r,xb;
}Que[100010];
bool cmp(node a,node b){
	return a.r<b.r;
}
vector<int> g[N];

inline ll ls(ll x){return x<<1;}
inline ll rs(ll x){return x<<1|1;}
inline void update(ll pos,ll l,ll r,ll p,ll k){ 
// nl~nr的最大值修改为max(原来的,k) 
	ans[p] = max(k,ans[p]);
    if(l == r)   return;
    ll mid = (l+r)>>1;
    if(pos <= mid)
		update(pos,l,mid,ls(p),k);
    else update(pos,mid+1,r,rs(p),k);
}
void query(ll q_x,ll q_y,ll l,ll r,ll p){
    if(q_x <= l&&r <= q_y) {
		res = max(res,ans[p]);
		return;
    }
    ll mid = (l+r)>>1;
    if(q_x <= mid)  query(q_x,q_y,l,mid,ls(p)); 
    if(q_y > mid)   query(q_x,q_y,mid+1,r,rs(p));
	return;
}
int main(){
	freopen("1.in","r",stdin);
	scanf("%lld%lld%lld",&n,&Q,&m);
	for(int i = 1;i <= n;i ++) scanf("%lld",&a[i]);
	for(int i = 1;i <= n;i ++) scanf("%lld",&b[i]),bpos[b[i]] = i;
	for(int i = 1;i <= m;i ++) scanf("%lld",&K[i]);
	for(int i = 0;i < Q;i ++) scanf("%lld%lld",&Que[i].l,&Que[i].r),Que[i].xb = i;
	sort(Que,Que+Q,cmp);
	memset(re,0,sizeof re);
	memset(ans,0,sizeof ans);
	ll s=1,l,r,b2,b2pos;
	for(int i = 0;i < Q;i ++){
		l = Que[i].l;
		r = Que[i].r;
		for(ll j = s;j <= r;j ++){
			for(int k = 1;k <= m;k ++){
				b2 = b[j] + K[k];
				if(b2 <= n){ //防止下一条越界 
					b2pos = bpos[b2];
					if(b2pos < j && a[b2pos]+a[j] > re[b2pos]){ 
					//更新前边的最大值。为什么不和re[j]比较?
					//因为更新的时候会从小更新到大.
						re[b2pos] = a[b2pos] + a[j];
						update(b2pos,1,n,1,re[b2pos]);
					}	
				}
				b2 = b[j] - K[k];
				if(b2 >= 1){
					b2pos = bpos[b2];
					if(b2pos < j && a[b2pos]+a[j] > re[b2pos]){
						re[b2pos] = a[b2pos] + a[j];
						update(b2pos,1,n,1,re[b2pos]);
					}
				} 
			}
		}
		res = 0;
		query(l,r,1,n,1);
		tans[Que[i].xb] = res;
		s = r;
	}
	for(int i = 0;i < Q;i ++)
		printf("%lld\n",tans[i]);
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值