序列

序列
这道题深刻地论证了分治算法的妙处,也形象地说明了序列题最好用分治算法
——————————————————————————————————————————————
本题正解的思路有点难想,强行构造出的分治合并方法非常巧妙(ZZ的解法)
——————————————————————————————————————————————
分 治 出 [ l , r ] , 取 m i d , 想 办 法 在 O ( n ) 的 时 间 里 合 并 分治出[l,r],取mid,想办法在O(n)的时间里合并 [l,r]midO(n)
分 类 讨 论 : 分类讨论:

  1. 若最小值在左边,即需要保证右边取的数都小于此值
  2. 若最小值在右边,即需保证左边取的数都小于此值
  3. 为了方便,我们把左右都存在最小值的归为以上某类讨论

但是由于序列并不单调,所以分类讨论之后不好直接做
——————————————————————————————————————————————

构造

由于是找最小值,所以我们用最小值来算比较方便
构 造 s u f 数 组 和 p r e 数 组 , 使 m i d 左 边 s u f 非 严 格 单 调 递 增 , 右 边 p r e 非 严 格 单 调 递 减 构造suf数组和pre数组,使mid左边suf非严格单调递增,右边pre非严格单调递减 sufpre使midsufpre
这样,就可以二分找出想要的值,
将 [ l , m i d ] 枚 举 两 次 , 一 次 从 大 到 小 , 一 次 从 小 到 大 , 这 样 就 能 很 快 更 新 出 最 值 ( 当 然 也 可 以 左 右 各 枚 举 一 次 , 但 这 样 要 写 两 个 二 分 ) 将[l,mid]枚举两次,一次从大到小,一次从小到大,这样就能很快更新出最值(当然也可以左右各枚举一次,但这样要写两个二分) [l,mid]()
——————————————————————————————————————————————

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N=100010;
int n;
int a[N],D;
int suf[N],pre[N],pos,tmp,x[N],s[1<<21];//不能开N*10,因为异或运算可能超过原数 
int find(int l,int r,int val){
	while(l<r){	
	    int mid=(l+r)>>1;
		if(pre[mid]>=val) l=mid+1;
		else r=mid;
	}
	return l;
}
ll dfs(int l,int r){
	ll ans=0;
	if(l==r) {return (a[l]==D);}
	int mid=(l+r)>>1;
	suf[mid]=a[mid],pre[mid+1]=a[mid+1],x[mid+1]=0;
	for(int i=mid+2;i<=r;i++) pre[i]=min(pre[i-1],a[i]),x[i]=(pre[i]^a[i]);	
	for(int i=mid-1;i>=l;i--) suf[i]=min(suf[i+1],a[i]);
	pos=mid+1;
	for(int i=mid;i>=l;i--){
		int tmp=find(mid+1,r,suf[i]);
		if(pre[r]>=suf[i]) tmp=r+1;  
		for(int j=pos;j<tmp;j++) s[a[j]]++;
	    ans+=s[(D^a[i]^suf[i])],pos=tmp;
 	} 	
 	for(int i=mid+1;i<=r;i++){s[a[i]]=0;}pos=r;
 	for(int i=l;i<=mid;i++){
 		int tmp=find(mid+1,r,suf[i]);
 		if(pre[r]>=suf[i]) tmp=r+1;
 		for(int j=pos;j>=tmp;j--) s[x[j]]++;//
 		ans+=s[(D^a[i])],pos=tmp-1;
 	}
 	for(int i=mid+1;i<=r;i++){s[x[i]]=0;}
	 return  ans+dfs(l,mid)+dfs(mid+1,r);
}
int main(){
	scanf("%d%d",&n,&D);
	for(int i=1;i<=n;i++){scanf("%d",&a[i]);}
	ll ansout=dfs(1,n);
	printf("%lld",ansout);
} 

——————————————————————————————————————————————

序列题先分治,后面想怎么用较短的时间以左边合并

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值