题解-数列分块入门

数列分块入门

    • 题目
    • 代码

题目

Description
给出一个长为n的数列,以及n个操作,操作涉及区间加法,
询问区间内小于某个值x的前驱(比其小的最大元素)。
Input
第一行输入一个数字n。
第二行输入n个数字,第ii个数字为a_i,以空格隔开。
接下来输入n行询问,每行输入四个数字opt、l、r、c,以空格隔开。
若opt=0,表示将位于[l,r]的之间的数字都加c。
若opt=1,表示询问[l,r]中c的前驱的值(不存在则输出-1)。
1<=n<=100000,−231<=others<=231
Output
对于每次询问,输出一行一个数字表示答案。
Sample Input

4
1 2 2 3
0 1 3 1
1 1 4 4
0 1 2 2
1 1 2 4

Sample Output

3
-1

代码

#pragma GCC optimize(3)
#include
using namespace std;
const int maxn=100010;
int a[maxn],s[maxn],t[maxn];
int tag[400];
vector<int>v[400];
void rebuild(int x) 
{
	v[x].clear();
	for(int i=t[x-1]+1;i<=t[x];i++)
		v[x].push_back(a[i]+=tag[x]);
	sort(v[x].begin(),v[x].end());
	tag[x]=0;
}
int main() 
{
	int n,T,opt,l,r,c;
	scanf("%d",&n);
	T=sqrt(n);
	for(int i=1;i<=n;i++) 
	{
		scanf("%d",a+i);
		s[i]=(i-1)/T+1;//属于哪一块 
		t[s[i]]=i;//记录了每一个块的最大i值 
		v[s[i]].push_back(a[i]);
	}
	for(int i=1;i<=s[n];i++)
		sort(v[i].begin(),v[i].end());
	for(int X=1;X<=n;X++) 
	{
		scanf("%d%d%d%d",&opt,&l,&r,&c);
		if(opt==0) 
		{
			if(s[l]>=s[r]-1)//左右端点在同一块或是相邻块 
			{
				for(int i=l;i<=r;i++)a[i]+=c;
				rebuild(s[l]);
				if(s[l]!=s[r])rebuild(s[r]);
			}
			else //左右边角单独做,中间整块做 
			{
				for(int i=l;i<=t[s[l]];i++)a[i]+=c;
				rebuild(s[l]);
				for(int i=s[l]+1;i<=s[r]-1;i++)tag[i]+=c;
				for(int i=t[s[r]-1]+1;i<=r;i++)a[i]+=c;
				rebuild(s[r]);
			}
		}
		else //查询 
		{
			int ans=-1;
			if(s[l]>=s[r]-1) 
			{
				for(int i=l;i<=r;i++)
					if(a[i]+tag[s[i]]<c)ans=max(ans,a[i]+tag[s[i]]);
			}
			else 
			{
				for(int i=l;i<=t[s[l]];i++)
					if(a[i]+tag[s[i]]<c)ans=max(ans,a[i]+tag[s[i]]);
				for(int i=s[l]+1;i<=s[r]-1;i++) 
				{
					int t=lower_bound(v[i].begin(),v[i].end(),c-tag[i])-v[i].begin();
					if(t>0)ans=max(ans,v[i][t-1]+tag[i]);
				}
				for(int i=t[s[r]-1]+1;i<=r;i++)
					if(a[i]+tag[s[i]]<c)ans=max(ans,a[i]+tag[s[i]]);
			}
			printf("%d\n",ans);
		}
	}
	return 0;
}


//利用set中的lowerbound进行查找第一个大于等于指定c的值 
#pragma GCC optimize(3)
#include
#include
#define N 100001
using namespace std;
set<int> tree[1100];
int n,m,pos[N];
int s[N],tag[N];
void read(int &x)
{
    char ch; bool ok;
    for(ok=0,ch=getchar(); !isdigit(ch); ch=getchar()) if(ch=='-') ok=1;
    for(x=0; isdigit(ch); x=x*10+ch-'0',ch=getchar()); if(ok) x=-x;
}
void add(int l,int r,int c)
{
    for(int i=l;i<=min(pos[l]*m,r);i++)
    {
        tree[pos[i]].erase(s[i]);
		s[i]+=c;
        tree[pos[i]].insert(s[i]);
    }
    if(pos[l]!=pos[r])
    for(int i=(pos[r]-1)*m+1;i<=r;i++)
	{
        tree[pos[i]].erase(s[i]);
		s[i]+=c;
        tree[pos[i]].insert(s[i]);
    }
    for(int i=pos[l]+1;i<=pos[r]-1;i++)
	    tag[i]+=c;
}
int findfront(int l,int r,int c)
{
    int ans=-1,num;
    for(int i=l;i<=min(pos[l]*m,r);i++)//不成块的区间暴力操作 
    {
        num=tag[pos[i]]+s[i];
        if(num<c)
		   ans=max(ans,num);//找出比c小的值 
    }
    if(pos[l]!=pos[r])
    for(int i=(pos[r]-1)*m+1;i<=r;i++)
    {
        num=tag[pos[i]]+s[i];
        if(num<c)
		   ans=max(ans,num);//找出比c小的值 
    }
    for(int i=pos[l]+1;i<=pos[r]-1;i++)
    //成块的区间,其内部元素值并没有发生变化,只是加上了lazy标记 
    {
        int t=c-tag[i];
        set<int>::iterator k=tree[i].lower_bound(t);
        if(k==tree[i].begin())
		    continue;
        k--;
        ans=max(ans,*k+tag[i]);
    }
    return ans;
}
int main()
{
    read(n);m=sqrt(n);
    for(int i=1;i<=n;i++)
        pos[i]=(i-1)/m+1;  //数字i在哪一个块中 
    for(int i=1;i<=n;i++)
	    read(s[i]),tree[pos[i]].insert(s[i]);
    for(int i=1;i<=n;i++)
    {
        int way,l,r,c;
        read(way),read(l),read(r),read(c);
        if(way==0) //[l,r]的之间的数字都加c
            add(l,r,c);
        if(way==1) //询问[l,r]中c的前驱的值
            printf("%d\n",findfront(l,r,c));
    }
    return 0;
}

你可能感兴趣的:(题解)