分块进阶2

P4109 [HEOI2015] 定价

可以想到预处理一段区间最小「荒谬值」的数
但是如果记录每一个「块」的左右端点,和每个点所属的「块」空间不够
所以可以直接对于每个点动态处理
对于点 \(x\) 它所处的「块」就是 \(\lfloor \frac{x}{len}\rfloor+1\)
对于「块」\(p\) 它的左端点$$(p-1)*len+1$$

右端点是$$ p*len $$
提前预处理「块」内的答案,查询时对于散块暴力求

#include<bits/stdc++.h>
#define int long long
#define fore(i,a,b) for( int i=(a); i<=(b); ++i)
#define repe(i,a,b) for( int i=(a); i>=(b); --i)
#define L(A) (A-1)*len+1
#define R(A) A*len
using namespace std;
const int N=1e6+10;
int n;
int len=1e4;
int Min[N];
int numlen(int x){
	int s=0;
	while(x){s++;x/=10;}
	return s;
}
int count(int x){
	while(x%10==0){x/=10;}
	if(x%10==5)return numlen(x)*2-1;
	return numlen(x)*2;
}
int query(int l,int r){
	int p=(l/len)+1,q=(r/len)+1;
	int minn=1e9,res=0;
	if(p==q){
		for(int i=l;i<=r;i++){
			int x=count(i);
			if(x<minn){
				minn=x;
				res=i;
			}
		}
		return res;
	}
	for(int i=l;i<p*len;i++){
		int x=count(i);
		if(x<minn){
			minn=x;
			res=i;
		}
	}
	for(int i=(q-1)*len;i<=r;i++){
		int x=count(i);
		if(x<minn){
			minn=x;
			res=i;
		}
	}
	for(int i=p*len;i<(q-1)*len;i+=len){
		int x=count(i);
		if(x<minn){
			minn=x;
			res=i;
		}
	}
	return res;
}
signed main(){
	ios::sync_with_stdio(false);
//	cerr<<count(5000)<<' '<<count(4000)<<'\n'; 
	cin>>n;
	while(n--){
		int l,r;
		cin>>l>>r;
		cout<<query(l,r)<<'\n';
	} 
	return 0;
}



由乃打扑克

区间的修改很容易做,重点是区间查询
发现可以实现一个区间小于一个值的数的个数 查询,可以参见这里
那么要查询一个区间的第 \(k\) 小值,可以考虑二分一个 \(mid\) ,用之前的方法记录小于 \(mid\) 的值 如果为 \(k-1\) 那么说明这个值正好是 第 \(k\) 小的值

#include<bits/stdc++.h>
#define int long long
#define fore(i,a,b) for( int i=(a); i<=(b); ++i)
#define repe(i,a,b) for( int i=(a); i>=(b); --i)
using namespace std;
const int N=1e5+10;
int n,T; 
int a[N],pos[N],L[N],R[N],lazy[N];
bool vis[N];
vector<int>G[N];
void init(){
	int len=sqrt(n);
	int num=(n+len-1)/len;
	for(int i=1;i<=num;i++){
		L[i]=(i-1)*len+1;
		R[i]=i*len; 
	}
	R[num]=n;
	for(int i=1;i<=num;i++){
		for(int j=L[i];j<=R[i];j++){
			pos[j]=i;
			G[i].push_back(a[j]);
		}
		sort(G[i].begin(),G[i].end());
	}
	return; 
}
void EA(int p){
	G[p].clear();
	for(int i=L[p];i<=R[p];i++){
		a[i]+=lazy[p];
		G[p].push_back(a[i]);
	} 
	sort(G[p].begin(),G[p].end());
	vis[p]=0;
	lazy[p]=0;
	return;
}
void update(int l,int r,int k){
	int p=pos[l],q=pos[r];
	if(p==q){
		for(int i=l;i<=r;i++)a[i]+=k;
		vis[p]=1;
		return;
	}
	for(int i=p+1;i<=q-1;i++){
		lazy[i]+=k;
	}
	for(int i=l;i<=R[p];i++)a[i]+=k;
	for(int i=L[q];i<=r;i++)a[i]+=k;
	vis[p]=vis[q]=1;
	return;
}
int ch(int l,int r,int k){
	int p=pos[l],q=pos[r];
	int ans=0;
	if(p==q){
		for(int i=l;i<=r;i++){
			if(a[i]+lazy[p]<k)ans++;
		}
		return ans;
	} 
	for(int i=l;i<=R[p];i++){
		if(a[i]+lazy[p]<k)ans++;
	}
	for(int i=L[q];i<=r;i++){
		if(a[i]+lazy[q]<k)ans++;
	}
	for(int i=p+1;i<=q-1;i++){
		if(G[i][0]>=k-lazy[i])continue;
		int tmp=G[i].size()-1;
		if(G[i][tmp]<k-lazy[i]){
			ans+=R[i]-L[i]+1;
			continue;
		}
		ans+=lower_bound(G[i].begin(),G[i].end(),k-lazy[i])-G[i].begin();
	}
	return ans;
}
int query(int l,int r,int k){
	int p=pos[l],q=pos[r];
	int lt=1e9,rt=-1e9;
	for(int i=p;i<=q;i++){
		if(vis[i])EA(i);
		lt=min(lt,G[i][0]+lazy[i]);
		rt=max(rt,G[i][G[i].size()-1]+lazy[i]);
	}
	lt--,rt++;
	if(k>r-l+1)return -1;
	while(lt+1<rt){
		int mid=(lt+rt)/2;
		int tmp=ch(l,r,mid);
		if(tmp>k-1)rt=mid;
		else lt=mid;
	}
	return rt-1;
}
signed main(){
	ios::sync_with_stdio(false);
	cin>>n>>T;
	for(int i=1;i<=n;i++)cin>>a[i];
	init();
	while(T--){
		int orr,l,r,k;
		cin>>orr>>l>>r>>k;
		if(orr==1)
			cout<<query(l,r,k)<<'\n';
		else update(l,r,k);
	}
	return 0;
}

弹飞绵羊

预处理

预处理每一个位置( \(i\) )从这个位置( \(i\) )跳出(\(i\))所处的「块」(\(pos[i]\)) 需要的步数 (\(step[i]\)) 和跳出它所处的「块」之后到的位置 (\(to[i]\))
具体来说一个位置如果绵羊在这里,那它下一次会出现的位置在就是当前位置加上这个位置的弹力装置系数

\[i+a[i] \]

可以到这枚举,如果这个位置跳完之后到了另一个「块」那么显然

\[step[i]=1 \]

\[to[i]=i+a[i] \]

因为它一步跳出了所处「块」
否则就将一步跳到的位置的答案传递到当前位置,也就是

\[step[i]=step[i+a[i]]+1 \]

\[to[i]=to[i+a[i]] \]

修改

这样预处理在修改时只要将需改位置的「块」的 \(step\)\(to\) 更新
注意更新和预处理都要从后往前遍历,这样传递的答案一定已经处理过了

查询

一个位置 \(x\) 一直往后跳

\[x=to[x] \]

在将 \(step[x]\) 累加起来
\(x\) 超过 \(n\) 是说明这只绵羊被弹飞(糖飞)返回答案

#include<bits/stdc++.h>
#define int long long
#define fore(i,a,b) for( int i=(a); i<=(b); ++i)
#define repe(i,a,b) for( int i=(a); i>=(b); --i)
using namespace std;
const int N=1e6+10; 
int n;
int L[N],R[N],pos[N];
int to[N],a[N],step[N];
void init(){
	int len=sqrt(n);
	int num=(n+len-1)/len;
	fore(i,1,num){
		L[i]=(i-1)*len+1;
		R[i]=i*len;
	}
	if(R[num]>n)R[num]=n;
	fore(i,1,num){
		repe(j,R[i],L[i]){
			pos[j]=i;
		}
	}
	repe(i,n,1){
		if(pos[a[i]+i]!=pos[i]){
			step[i]=1;
			to[i]=a[i]+i;
		}
		else{
			to[i]=to[a[i]+i];
			step[i]=step[a[i]+i]+1;
		}
	}
}
int query(int x){
	int num=0;
	while(x<=n){
		num+=step[x];
		x=to[x];
	}
	return num;
}
void update(int x,int y){
	int p=pos[x];
	a[x]=y;
	for(int i=R[p];i>=L[p];i--){
		if(pos[i+a[i]]!=pos[i]){
			to[i]=i+a[i];
			step[i]=1;
		}
		else{
			to[i]=to[i+a[i]];
			step[i]=step[i+a[i]]+1;
		}
	}
}
signed main(){
	ios::sync_with_stdio(false);
	cin>>n;
	fore(i,1,n)cin>>a[i];
	init();
//	fore(i,1,n)cout<<to[i]<<' ';cout<<'\n';
//	fore(i,1,n)cout<<step[i]<<' ';cout<<'\n';
	int T;
	cin>>T;
	while(T--){
		int orr,x,y;
		cin>>orr>>x;
		x++;
		if(orr==1)cout<<query(x)<<'\n';
		else{
			cin>>y;
			update(x,y);
		}
//		fore(i,1,n)cout<<to[i]<<' ';cout<<'\n';
//		fore(i,1,n)cout<<step[i]<<' ';cout<<'\n';
	}
	return 0;
}



posted @ 2025-08-04 15:14  wmq2012  阅读(7)  评论(0)    收藏  举报