【吉如一线段树】JZOJ6270. 【省赛模拟8.10】序列

本文深入探讨了一种高效的数据结构——吉如一线段树,用于处理区间查询和更新问题。通过实例分析,展示了如何利用该数据结构进行区间取最大值和求和操作,特别适用于区间加减操作频繁的场景。文章详细解释了吉如一线段树的实现原理,包括节点信息维护、区间更新和查询算法,并提供了一个具体的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

在这里插入图片描述
ai,n<=2e5

Solution

  • 考虑按照答案分类,假如ddd的倍数为a[1],a[2],..a[k−1],a[k]a[1],a[2],..a[k-1],a[k]a[1],a[2],..a[k1],a[k],那么区间被1…(a[k-1]-1),(a[2]+1)…n,(a[1]+1)…(a[n]-1)这三个区间包含的区间的答案至少为d,也就是要维护区间取MAX,并求和。
  • 最后求所有区间的答案。
  • 将操作区间左到右排序,用一个吉如一线段树(裸的)直接维护这个操作就好了。
  • 实际上由于每一次对一个前缀取MAX,区间的值实际上是单调不升的,二分一下就好了(用一个set).

吉如一线段树

  • 据说是nlogn~nlog2n的。
  • 我们只考虑求最大值(最小值类似),以及区间求和。
  • 每一个节点记录一个最小值和严格次小值(初值为inf),区间和以及区间最小值的个数。
  • 假设当前修改的是v,v如果小于等于最小值肯定不用考虑。
  • v如果大于最小值并严格小于次小值,那么考虑将最小值的那一部分改为v(打一个对区间最值加或减的标记)再计算贡献。
  • 否则大于等于次小值就递归下去。

证明(upd on 2021.03.24)

  • 对于没有区间加减的,可以简单地设势能为每一个线段树节点的区间内不同的数的个数之和,那么每一次操作会将最小值与次小值合并,势能会减少。并且每一个更新的区间影响到的节点势能最多只会增加1,因此每一次增加log nlog\ nlog n,时间复杂度就是O(n log n)O(n\ log\ n)O(n log n)的。
  • 但是对于有区间加减的这样就不太好证明了,因此还可以考虑设势能为有多少个节点的最值与父亲相同,可以发现在暴力递归的情况下势能会减少1,区间加减的时候最多更新到logloglog个点的势能,使得每一次最多加上logloglog,每一个势能最多用log nlog\ nlog n消除,上限是O(n log2n)O(n\ log^2n)O(n log2n),但是似乎并不能卡满。
#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
#define maxn 200005
#define maxm 1000005
#define LL long long 
using namespace std;

int n,i,j,k,a[maxn],mx,tot,w;
int p0[maxn],p1[maxn],q0[maxn],q1[maxn];
int u0[maxn],u1[maxn],v0[maxn],v1[maxn];
struct opr{
	int l,r,x;
	opr(int _l=0,int _r=0,int _x=0){l=_l,r=_r,x=_x;}
} p[20*maxn];
int cmp(opr a,opr b){return a.l<b.l;}

LL t[maxm],ans;
int mi0[maxm],mi1[maxm],cnt[maxm],tag[maxm];

void Min(int &x,int y){x=min(x,y);}
void Max(int &x,int y){x=max(x,y);}

void add0(int i,int x){
	if (x<p0[i]) p1[i]=p0[i],p0[i]=x; else 
	if (x<p1[i]) p1[i]=x;
	if (x>q0[i]) q1[i]=q0[i],q0[i]=x; else
	if (x>q1[i]) q1[i]=x;
}

void add1(int i,int x,int t){
	if (t==0){
		if (x<u0[i]) u1[i]=u0[i],u0[i]=x; else 
		if (x<u1[i]) u1[i]=x;		
	}
	if (t==1){
		if (x>v0[i]) v1[i]=v0[i],v0[i]=x; else
		if (x>v1[i]) v1[i]=x;		
	}
}

void add2(int i,int j){
	if (p0[j]!=maxn) add1(i,p0[j],0);
	if (p1[j]!=maxn) add1(i,p1[j],0);
	if (q0[j]) add1(i,q0[j],1);
	if (q1[j]) add1(i,q1[j],1);
}

void insert(int l,int r,int x){
	if (l<=r) tot++,p[tot]=opr(l,r,x);
}

void maketree(int x,int l,int r){
	t[x]=0,mi0[x]=0,mi1[x]=maxn,tag[x]=0,cnt[x]=r-l+1;
	if (l==r) return;
	int mid=(l+r)>>1;
	maketree(x<<1,l,mid),maketree(x<<1|1,mid+1,r);
}

void doit(int x,int v){
	if (v<=mi0[x]) return;
	t[x]+=(v-mi0[x])*cnt[x];
	mi0[x]=v;
}

void downtag(int x,int l,int r){
	doit(x,tag[x]);
	if (l<r){
		Max(tag[x<<1],tag[x]);
		Max(tag[x<<1|1],tag[x]);
	}
	tag[x]=0;
}

void upd(int x){
	int l=x<<1,r=x<<1|1;
	t[x]=t[l]+t[r];
	mi0[x]=min(mi0[l],mi0[r]);
	mi1[x]=min(mi1[l],mi1[r]);
	if (mi0[x]^mi0[l]) Min(mi1[x],mi0[l]);
	if (mi0[x]^mi0[r]) Min(mi1[x],mi0[r]);
	cnt[x]=(mi0[l]==mi0[x])*cnt[l]+(mi0[r]==mi0[x])*cnt[r];
}

void change(int x,int l,int r,int ll,int rr,int v){
	if (tag[x]) downtag(x,l,r);
	if (l>rr||r<ll||mi0[x]>=v) return;
	if (ll<=l&&r<=rr&&mi1[x]>v){
		tag[x]=max(tag[x],v),downtag(x,l,r); 
		return;
	}
	int mid=(l+r)>>1;
	change(x<<1,l,mid,ll,rr,v);
	change(x<<1|1,mid+1,r,ll,rr,v);
	upd(x);
}

LL find(int x,int l,int r,int ll,int rr){
	if (tag[x]) downtag(x,l,r);
	if (l>rr||r<ll) return 0;
	if (ll<=l&&r<=rr) return t[x];
	int mid=(l+r)>>1;
	return find(x<<1,l,mid,ll,rr)+find(x<<1|1,mid+1,r,ll,rr);
}

int main(){
	scanf("%d",&n);
	for(i=1;i<maxn;i++) p0[i]=p1[i]=maxn,q0[i]=q1[i]=0;
	for(i=1;i<=n;i++) {
		scanf("%d",&a[i]);
		add0(a[i],i),mx=max(mx,a[i]);
	}
	for(i=1;i<=mx;i++) {
		u0[i]=u1[i]=maxn,v0[i]=v1[i]=0;
		for(j=i;j<=mx;j+=i) add2(i,j);
		insert(u0[i]+1,v0[i]-1,i);
		insert(1,v1[i]-1,i);
		insert(u1[i]+1,n,i);
	}
	sort(p+1,p+1+tot,cmp);
	maketree(1,1,n); j=1;
	for(i=1;i<=n;i++){
		for(;j<=tot&&p[j].l==i;j++) 
			change(1,1,n,p[j].l,p[j].r,p[j].x);
		ans+=find(1,1,n,i,n);
	}
	printf("%lld\n",ans);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值