首页 > 其他分享 >Ynoi 大分块选做

Ynoi 大分块选做

时间:2024-03-12 18:35:07浏览次数:21  
标签:le 分块 ll Ynoi ry 选做 maxn mx define

第二分块

link

CF 版本

题意:给出一个序列 \(a_{1...n}\),有 \(m\) 次操作。每次:

  • 修改:给出 \(l,r,x\),表示把区间 \([l,r]\) 中 \(>x\) 的数减去 \(x\)

  • 查询:给出 \(l,r,x\),求 \([l,r]\) 中有多少个数 \(=x\)

\(1\le n\le 10^6,\space 1\le m\le 5\times 10^5,\space 0\le a_i,x\le 10^5+1\),时间限制 \(7.5\text{s}\),空间限制 \(64\text{MB}\)

考虑分块,块长为 \(B\)。

发现值域只有 \(0...10^5+1\),我们需要从这方面入手。

考虑整块的操作,每个块维护 \(t_x\) 表示初始为 \(x\) 的数值在操作后变成的数,以及 \(s_x\) 表示当前 \(=x\) 的数字个数。

设块内数字最大值为 \(mx\),减去的数为 \(x\),分类讨论:

  • \(x\ge\dfrac {mx}2\):每个数减一次之后一定 \(\le x\),值域上遍历一下 \(x+1...mx\),更新这些值的 \(t\)。

  • \(x<\dfrac {mx}2\):此时如果更新 \(t\) 会发现无法处理。考虑让 \(\le x\) 的数加上 \(x\),然后打个整体减 \(x\) 标记。

对于散块的操作,暴力更新块内值,然后重新初始化 \(t\) 和 \(s\)。

发现初始化难搞,我们可以把 \(t\) 数组映射到块内元素上。具体的,设 \(r_x\) 表示块内第一个 \(=x\) 的数出现位置,设 \(t_i\) 表示 \(a_i\) 当前的值为 \(a_{t_i}\)。

用并查集维护 \(t\)。对于根 \(i\),因为 \(t_i=i\) 不能递归,维护 \(v_i\) 表示这个根的数值即可。

但是空间是 \(O(\dfrac {n^2}B)\) 的,无法通过。考虑一个 trick,因为每个块是独立的,所以可以在最外层枚举块,然后依次扫描每个操作。

点击查看代码
#include<bits/stdc++.h>
#define ll int
#define ull unsigned ll
#define mkp make_pair
#define fi first
#define se second
#define pir pair<ll,ll>
#define pb push_back
using namespace std;
const ll maxn=1e6+10;
ll B=1000,op[maxn][4],L,R,mx,tag;
ll n,m,a[maxn],d[maxn],rt[maxn],val[maxn],siz[maxn],ans[maxn],o[maxn],g;
inline ll find(ll x){
	ll &dx=d[x];
	return x==dx? x:dx=find(dx);
}
inline void merg(const ll &x,const ll &y){
	ll &rx=rt[x], &ry=rt[y];
	if(ry) d[rx]=ry;
	else{
		ry=rx;
		val[ry]=y;
	}
	siz[y]+=siz[x], rx=siz[x]=0;
}
inline void build(){
	mx=tag=0;
	for(register ll i=L;i<=R;++i){
		ll ai=a[i];
		mx=max(mx,ai);
		if(rt[ai]) d[i]=rt[ai];
		else rt[ai]=d[i]=i, val[i]=ai;
		++siz[ai];
	}
}
inline void block_change(const ll &x){
	if(mx-tag>2*x){
		for(register ll i=tag+1;i<=tag+x;++i)
			merg(i,i+x);
		tag+=x;
	} else{
		for(register ll i=tag+x+1;i<=mx;++i)
			merg(i,i-x);
		mx=min(mx,tag+x);
	}
}
inline void clr(){
	for(ll i=L;i<=R;++i){
		a[i]=val[find(i)];
		rt[a[i]]=siz[a[i]]=0;
		a[i]-=tag;
	}
	for(ll i=L;i<=R;i++) val[i]=d[i]=0;
}
inline void part_change(const ll &l,const ll &r,const ll &x){
	for(ll i=L;i<=R;++i){
		a[i]=val[find(i)];
		rt[a[i]]=siz[a[i]]=0;
		a[i]-=tag;
	}
	for(ll i=L;i<=R;i++) val[i]=d[i]=0;
	for(ll i=l;i<=r;++i)
		if(a[i]>x) a[i]-=x;
	mx=tag=0;
	for(register ll i=L;i<=R;++i){
		ll ai=a[i];
		mx=max(mx,ai);
		if(rt[ai]) d[i]=rt[ai];
		else rt[ai]=d[i]=i, val[i]=ai;
		++siz[ai];
	}
}
inline void query(const ll &l,const ll &r,const ll &x,ll &res){
	if(l==L&&r==R){
		if(x+tag<=1e5+1) res+=siz[x+tag]; return;
	}
	for(ll i=l;i<=r;++i)
		if(val[find(i)]-tag==x) ++res;
}
inline void rd(ll &x){
	char c;
	while(!isdigit(c=getchar())) ;
	x=c-'0';
	while(isdigit(c=getchar())) x=x*10+c-'0';
}
int main(){
	rd(n), rd(m); B=sqrt(n);
	for(register ll i=1;i<=n;++i) rd(a[i]);
	for(register ll i=1;i<=m;++i){
		rd(op[i][0]), rd(op[i][1]), rd(op[i][2]), rd(op[i][3]);
	}
	ll o=0;
	while(o<n){
		L=o+1, R=min(n,o+=B);
		build();
		for(ll j=1;j<=m;++j){
			ll t=op[j][0], l=op[j][1], r=op[j][2], x=op[j][3];
			if(t==1){
				if(x==0||r<L||l>R) continue;
				if(l<=L&&R<=r){
					block_change(x);
				} else{
					part_change(max(l,L),min(r,R),x);
				}
			}
			else query(max(l,L),min(r,R),x,ans[j]);
		}
		clr();
	}
	for(register ll i=1;i<=m;i++)
		if(op[i][0]==2) printf("%d\n",ans[i]);
	return 0;
}

标签:le,分块,ll,Ynoi,ry,选做,maxn,mx,define
From: https://www.cnblogs.com/Sktn0089/p/18068974

相关文章

  • 取反(分块+二分)
    第2题   取反 查看测评数据信息给一个长度是n的数组,a[1],a[2],a[3],...a[n-1],a[n],初始时a数组中所有的元素都为0,下面有两种操作:1.指定一个区间[x,y],把a[x],a[x+1],...a[y]的值取反,即如果a[i]的值为1则把a[i]的值变为0,如果a[i]的值为0则把a[i]的值变为12.指定一个区......
  • 统计数量(分块+二分)
    第1题   统计数量 查看测评数据信息给一个长度是n的正整数数组,a[1],a[2],a[3],...a[n-1],a[n],其中1<=a[i]<=1000。现在在数组a上进行m次操作:1.Mxyz,表示对a数组的闭区间[x,y]内所有a[i]的值分别加上z2.Axyzz,询问a数组闭区间[x,y]内有多少a[i]的值大于等于z......
  • 分块
    与线段树类似。是暴力的优化版,详细见下两个链接https://www.cnblogs.com/milky-w/p/8447724.htmlhttps://www.cnblogs.com/Miracevin/p/9403620.html  板子:#include<bits/stdc++.h>#defineintlonglongusingnamespacestd;constintN=1e5+5;intn,m,a[N],s......
  • 分块小结
    分块概念就是把一个长序列分成\(\sqrt{n}\)个区间,分别维护每个区间内的信息和,然后查询时可以优化时间复杂度。还可以完成一些线段树完成不了的神秘操作,比如这道题。但是总体时间复杂度不如线段树,但它的扩展性比线段树还要强,因为分块中每个区间的信息和不需要具有传递性。怎......
  • P9989 [Ynoi Easy Round 2023] TEST_69 题解
    分析考虑线段树。\(20\)分统计节点懒标记,在每次询问之前统一下传\((lst,i-1)\)的修改懒标记,\(lst\)是上一次询问的位置。\(40\)分在统一下传的过程中打标记,如果当前节点的某个儿子所在子树中没有需要下传懒标记的节点,则不更新那个儿子的内容。\(70\)分注意到\(a\)......
  • P10149 [Ynoi1999] XM66F 题解
    分析考虑莫队。对于$a_i=k(l\lei\ler)$的下标集合$S_k$,当其加入一个新的下标$x$时,这个新下标对答案的贡献分两种情况。第一种,$x$最小。相邻从下标的间隔中产生的贡献是$\sum(|S_k|-i+1)\times(ans_{S_{k,i+1}}-ans_{S_{k,i}})$。画个图可以理解一下:第二中,$x$最......
  • P5609 [Ynoi2013] 对数据结构的爱
    题面传送门好像搞了个神秘做法。考虑离线扫描线,用一个fhq-Treap维护所有的询问现在长什么样,然后每次操作就整体加\(A_i\),\(\geqp\)的减去\(p\),这个可以分裂之后打整体标记,然后用那个值域相交的fhq-Treap合并实现。然后你发现这样就过了。构造一下卡不掉,于是考虑给这个......
  • 莫队与分块学习笔记
    分块思想介绍分块是一种思想,而不是一种数据结构。思想就是,将一块大的区间,转换成小的区间来处理。例如,在一个\(n\)长度上的数轴,我们可以将其分成\(\sqrtn\)个长度为\(\sqrtn\)的块来解决。典型问题对于一类很典型的问题,可以用分块来做。单点修改,区间查询这玩意咋......
  • dp&图论 杂题选做
    开个新坑qwq。upd:CSP前一周暂时停更。upd:暂时不会更了。P1099经典套路题。算法一:枚举。先dfs求出树的直径,再枚举直径上的每条路径,再dfs一遍求出最小偏心距即可。时间复杂度\(O(n^3)\),足以通过本题(由此可见本题有多水)。算法二:双指针。通过观察可以发现,在固定左端......
  • P4690 [Ynoi2016] 镜中的昆虫 题解
    题目链接:镜中的昆虫经典题了,我们首先回顾下颜色数的常见做法统计:对每个位置维护一个\(pre_i\),表示与当前位置相同的颜色上一次出现位置。那么我们分讨一下。查询\([l,r]\)得到颜色数,对于\(pre_i<l\)的\(i\)点,显然它就是这个区间内\(a_i\)对应颜色出现的第一个位置,我们......