【分块入门1-9】--分块大法好

分块一:

给出一个长为n的数列,以及n个操作,操作涉及区间加法,单点查值。

  普通分块水过

#include
#include
#include
#include
using namespace std;
int n, a[100100], len,tag[100100], posit[100100];
void change(int l, int r, int c) {
    if (posit[l]==posit[r]) {
        for (int i = l; i <= r; i++) {
            a[i] += c;
        }
        return;
    }
    for(int i=l; i<=posit[l]*len; i++) a[i] += c;
    for(int i=posit[l] + 1; i<= posit[r]-1; i++) tag[i] += c;
    for(int i=(posit[r] - 1)*len +1;i <= r; i++) a[i] += c;
}
int main() {
    scanf("%d", &n);
    len = sqrt(n);
    for (int i = 1; i <= n; i++) {
       scanf("%d", &a[i]);
	   posit[i] = (i - 1) / len + 1;
     }
    int opt, l, r, c;
    while (n--) {
        scanf("%d%d%d%d", &opt, &l, &r, &c);
        if (!opt) {
            change(l,r,c);
        } else {
            printf("%d\n",a[r]+tag[posit[r]]);
        }
    }
    return 0;
}

分块二:

给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的元素个数。

    块内sort,lower_bound,加一个数组,辅助残块sort,因为原序列不能变

#include
#include
#include
#include
#include
#define int long long
#define MAXN 100000
using namespace std;
int n,opt,a[MAXN],b[MAXN],pos[MAXN],tag[MAXN],len;
void change(int l,int r,int c)
{
	if(pos[l]==pos[r])
	{
		for(int i=l;i<=r;i++)a[i]+=c;
		for(int i=(pos[l]-1)*len+1;i<=min(pos[l]*len,n);i++)b[i]=a[i];
		sort(b+(pos[l]-1)*len+1,b+1+min(n,pos[l]*len));
		return;
	}
	for(int i=l;i<=pos[l]*len;i++)a[i]+=c;
	for(int i=(pos[l]-1)*len+1;i<=pos[l]*len;i++)b[i]=a[i];
	sort(b+(pos[l]-1)*len+1,b+1+pos[l]*len);
	for(int i=pos[l]+1;i<=pos[r]-1;i++)tag[i]+=c;
	for(int i=(pos[r]-1)*len+1;i<=r;i++)a[i]+=c;
	for(int i=(pos[r]-1)*len+1;i<=min(pos[r]*len,n);i++)b[i]=a[i];
	sort(b+(pos[r]-1)*len+1,b+1+min(pos[r]*len,n));
}
int query(int l,int r,int c)
{
	int ans=0;
	if(pos[l]==pos[r])
	{
		for(int i=l;i<=r;i++)
		{
			if(a[i]+tag[pos[i]]

  

 

分块三:

给出一个长为n的数列,以及n个操作,操作涉及区间加法,询问区间内小于某个值x的前驱(比其小的最大元素)。

 块内sort加lower_bound查询

#include 
#include 
#include 
#include 
#define N 500500
using namespace std;

inline long long read() {
    int x = 0, f = 1;
    char s = getchar();
    while (s < '0' || s > '9') {
        if (s == '-')
            f = -1;
        s = getchar();
    }
    while (s >= '0' && s <= '9') {
        x = x * 10 + s - '0';
        s = getchar();
    }
    return x * f;
}

int n, len;
int a[N], b[N];
int tag[N], lz[N];
int L[N], R[N];

inline void change(int l, int r, int c) {
    if (tag[l] == tag[r]) {
        for (int i = l; i <= r; i++) a[i] += c;
        for (int i = L[tag[l]]; i <= R[tag[r]]; i++) b[i] = a[i];
        sort(b + L[tag[l]], b + R[tag[l]] + 1);
        return;
    }
    for (int i = l; i <= R[tag[l]]; i++) a[i] += c;
    for (int i = L[tag[l]]; i <= R[tag[l]]; i++) b[i] = a[i];
    sort(b + L[tag[l]], b + R[tag[l]] + 1);
    for (int i = tag[l] + 1; i < tag[r]; i++) lz[i] += c;
    for (int i = L[tag[r]]; i <= r; i++) a[i] += c;
    for (int i = L[tag[r]]; i <= R[tag[r]]; i++) b[i] = a[i];
    sort(b + L[tag[r]], b + R[tag[r]] + 1);
}

inline int ask(int l, int r, int c) {
    int ans = -1;
    if (tag[l] == tag[r]) {
        for (int i = l; i <= r; i++)
            if (a[i] + lz[tag[i]] < c)
                ans = max(ans, a[i] + lz[tag[i]]);
        return ans;
    }
    for (int i = l; i <= R[tag[l]]; i++)
        if (a[i] + lz[tag[i]] < c)
            ans = max(ans, a[i] + lz[tag[i]]);
    for (int i = tag[l] + 1; i < tag[r]; i++) {
        int k = lower_bound(b + L[i], b + R[i] + 1, c - lz[i]) - b;
        if (k != L[i])
            if (b[k - 1] + lz[i] < c)
                ans = max(ans, b[k - 1] + lz[i]);
    }
    for (int i = L[tag[r]]; i <= r; i++)
        if (a[i] + lz[tag[i]] < c)
            ans = max(ans, a[i] + lz[tag[i]]);
    return ans;
}

int main() {
    n = read();
    int len = sqrt(n);
    for (int i = 1; i <= n; i++) tag[i] = (i - 1) / len + 1;
    for (int i = 1; i <= tag[n]; i++) L[i] = R[i - 1] + 1, R[i] = min(n, L[i] + len - 1);
    for (int i = 1; i <= n; i++) a[i] = read(), b[i] = a[i];
    for (int i = 1; i <= tag[n]; i++) sort(b + L[i], b + R[i] + 1);
    for (int i = 1; i <= n; i++) {
        int opt = read(), l = read(), r = read(), c = read();
        if (opt == 0)
            change(l, r, c);
        else
            printf("%d\n", ask(l, r, c));
    }
    return 0;
}

  

分块四:

给出一个长为n的数列,以及n个操作,操作涉及区间加法,区间求和。

     打标记,其余暴力

#include
#include
#include
#include
#include
#define MAXN 1000010
#define int long long
int a[MAXN],tag[MAXN],le[MAXN],ri[MAXN],sum[MAXN],val[MAXN],pos[MAXN],len;
void change(int l,int r,int c)
{
	if(pos[l]==pos[r])
	{
		for(int i=l;i<=r;i++)
		{
			a[i]+=c;val[pos[i]]+=c;
		}
		return;
	}
	for(int i=l;i<=ri[pos[l]];i++) a[i]+=c , val[pos[i]]+=c;
	for(int i=pos[l]+1;i <= pos[r]-1 ; i++)tag[i]+=c;
	for(int i=le[pos[r]];i<=r;i++) a[i]+=c , val[pos[i]]+=c;
}
int query(int l,int r,int c)
{
	int mod=c+1,ans=0;
	if(pos[l]==pos[r])
	{
		for(int i=l;i<=r;i++)ans+=(a[i]+tag[pos[i]]),ans%=mod;
		return ans;
	}
	for(int i=l;i<=ri[pos[l]];i++)ans+=(a[i]+tag[pos[i]]),ans%=mod;
	for(int i=pos[l]+1;i<=pos[r]-1;i++)
	{
		ans+=(val[i]+tag[i]*len);
		ans%=mod;
	}
	for(int i=le[pos[r]];i<=r;i++)ans+=(a[i]+tag[pos[i]]),ans%=mod;
	return ans;
}
signed main()
{
	int n;
	scanf("%lld",&n);
	len=sqrt(n);
	for(int i=1;i<=n;i++)
	{
		pos[i]=(i-1)/len+1;
		scanf("%lld",&a[i]);
		sum[i]=sum[i-1]+a[i];
	}
	for(int i=1;i<=pos[n];i++)
	{
		le[i]=(i-1)*len+1;
		ri[i]=i*len;
		val[i]=sum[ri[i]]-sum[le[i]-1];
	}
	for(int i=1,opt,l,r,c;i<=n;i++)
	{
		scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
		if(opt==0)change(l,r,c);
		if(opt==1)
		{
			printf("%lld\n",query(l,r,c));
		}
	}
	return 0;
}

分块五:

给出一个长为n的数列,以及n个操作,操作涉及区间开方,区间求和。

  线段树,标记维护区间是否都为一

#include
#include
#include
#include
#define MAXN 5100000
#define int long long
using namespace std;
long long tr[MAXN*4],n,a[MAXN*2];
bool vis[MAXN*4];
void updata(int k)
{
	tr[k]=tr[k<<1]+tr[k<<1|1];
	if(vis[k<<1]==1 && vis[k<<1|1]==1)vis[k]=1;
	return;
}
void change(int k,int l,int r,int x,int y)
{
	if(vis[k])return;
	if(l>=x && r<=y && l==r)
	{
		tr[k]=sqrt(tr[k]);
		if(tr[k]==1||tr[k]==0)vis[k]=1;
		return;
	}
	int mid=(l+r)>>1;
	if(x<=mid)change(k<<1,l,mid,x,y);
	if(y>mid)change(k<<1|1,mid+1,r,x,y);
	updata(k);
}
int ask(int k,int l,int r,int x,int y)
{
	if(l>=x && r<=y)return tr[k];
	int mid=(l+r)>>1,ans=0;
	if(x<=mid)ans+=ask(k<<1,l,mid,x,y);
	if(y>mid)ans+=ask(k<<1|1,mid+1,r,x,y);
	return ans;
}
void  build(int k,int l,int r)
{
	if(l==r)
	{
		tr[k]=a[l];
		if(a[l]==1||a[l]==0)vis[k]=1;
		return ;
	}
	int mid=(l+r)>>1;
	build(k<<1,l,mid);
	build(k<<1|1,mid+1,r);
	updata(k); 
}
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	build(1,1,n);
	for(int i=1,opt,l,c,r;i<=n;i++)
	{
		scanf("%lld%lld%lld%lld",&opt,&l,&r,&c);
		if(l>r)swap(l,r);
		if(opt==0)
		{
			change(1,1,n,l,r);
		}
		else
		{
			printf("%lld\n",ask(1,1,n,l,r));
		}
	}
	return 0;
}

 

分块六:

给出一个长为n的数列,以及n个操作,操作涉及单点插入,单点询问,数据随机生成。

  开一个vector 插入,pair ,

先说随机数据的情况

之前提到过,如果我们块内用数组以外的数据结构,能够支持其它不一样的操作,比如此题每块内可以放一个动态的数组,每次插入时先找到位置所在的块,再暴力插入,把块内的其它元素直接向后移动一位,当然用链表也是可以的。

查询的时候类似,复杂度分析略。

 

但是这样做有个问题,如果数据不随机怎么办?

如果先在一个块有大量单点插入,这个块的大小会大大超过√n,那块内的暴力就没有复杂度保证了。

还需要引入一个操作:重新分块(重构)

每根号n次插入后,重新把数列平均分一下块,重构需要的复杂度为O(n),重构的次数为√n,所以重构的复杂度没有问题,而且保证了每个块的大小相对均衡。
当然,也可以当某个块过大时重构,或者只把这个块分成两半。

#include
#include
#include
#include
#include
#define N 200000
using namespace std;
int pos[N],len,le[N],ri[N],n,a[N],st[N],m;
vector  ve[N];
pair  query(int b)
{
	int x=1;
	while(b>ve[x].size())
	{
		b-=ve[x].size();
		x++;
	}
	return make_pair(x,b-1);
}
void rebuild()
{
	int top=1;
	for(int i=1;i<=m;i++)
	{
		for(vector ::iterator j=ve[i].begin();j!=ve[i].end();j++)
		    st[++top]=*j;
			ve[i].clear();
	}
	int le=sqrt(top);
	for(int i=1;i<=top;i++)ve[(i-1)/le+1].push_back(st[i]);
	m=(top-1)/le+1;
}
void insert(int a,int b)
{
	pair  t=query(a);
	ve[t.first].insert( ve[t.first].begin() + t.second ,b);
//	if(ve[t.first].size()>20*len) rebuild();
}
int main()
{
//	freopen("a9.in","r",stdin);
//	 freopen("ans.out","w",stdout);
	scanf("%d",&n);
	len=sqrt(n);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=n;i++) ve[(i-1)/len+1].push_back(a[i]);
	int m=(n-1)/len+1;
	for(int i=1,opt,x,y,c;i<=n;i++)
	{
		scanf("%d%d%d%d",&opt,&x,&y,&c);
		if(opt==0)
		{
			insert(x,y);
		}
		if(opt==1)
		{
			pair  t = query(y);
			printf("%d\n",ve[t.first][t.second]);
		}
	}
	return 0;
}

 

分块七:

给出一个长为n的数列,以及n个操作,操作涉及区间乘法,区间加法,单点询问。

很显然,如果只有区间乘法,和分块入门 1 的做法没有本质区别,但要思考如何同时维护两种标记。

我们让乘法标记的优先级高于加法(如果反过来的话,新的加法标记无法处理)

若当前的一个块乘以m1后加上a1,这时进行一个乘m2的操作,则原来的标记变成m1*m2,a1*m2

若当前的一个块乘以m1后加上a1,这时进行一个加a2的操作,则原来的标记变成m1,a1+a2

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mod 10007
#define pi acos(-1)
#define inf 0x7fffffff
#define ll long long
using namespace std;
ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,blo;
int v[100005],bl[100005],atag[1005],mtag[1005];
void reset(int x)
{
    for(int i=(x-1)*blo+1;i<=min(n,x*blo);i++)
        v[i]=(v[i]*mtag[x]+atag[x])%mod;
    atag[x]=0;mtag[x]=1;
}
void solve(int f,int a,int b,int c)
{
    reset(bl[a]);
    for(int i=a;i<=min(bl[a]*blo,b);i++)
    {
        if(f==0)v[i]+=c;
        else v[i]*=c;
        v[i]%=mod;
    }
    if(bl[a]!=bl[b])
    {
        reset(bl[b]);
        for(int i=(bl[b]-1)*blo+1;i<=b;i++)
        {
            if(f==0)v[i]+=c;
            else v[i]*=c;
            v[i]%=mod;
        }
    }
    for(int i=bl[a]+1;i<=bl[b]-1;i++)
    {
        if(f==0)atag[i]=(atag[i]+c)%mod;
        else 
        {
            atag[i]=(atag[i]*c)%mod;
            mtag[i]=(mtag[i]*c)%mod;
        }
    }
}
int main()
{
    n=read();blo=sqrt(n);
    for(int i=1;i<=n;i++)v[i]=read();
    for(int i=1;i<=n;i++)bl[i]=(i-1)/blo+1;
    for(int i=1;i<=bl[n];i++)mtag[i]=1;        
    for(int i=1;i<=n;i++)
    {
        int f=read(),a=read(),b=read(),c=read();
        if(f==2)printf("%d\n",(v[b]*mtag[bl[b]]+atag[bl[b]])%mod);
        else solve(f,a,b,c);
    }
    return 0;
}

分块八:

给出一个长为n的数列,以及n个操作,操作涉及区间询问等于一个数c的元素,并将这个区间的所有元素改为c。

区间修改没有什么难度,这题难在区间查询比较奇怪,因为权值种类比较多,似乎没有什么好的维护方法。

模拟一些数据可以发现,询问后一整段都会被修改,几次询问后数列可能只剩下几段不同的区间了。

我们思考这样一个暴力,还是分块,维护每个分块是否只有一种权值,区间操作的时候,对于同权值的一个块就O(1)统计答案,否则暴力统计答案,并修改标记,不完整的块也暴力。

这样看似最差情况每次都会耗费O(n)的时间,但其实可以这样分析:

假设初始序列都是同一个值,那么查询是O(√n),如果这时进行一个区间操作,它最多破坏首尾2个块的标记,所以只能使后面的询问至多多2个块的暴力时间,所以均摊每次操作复杂度还是O(√n)。

换句话说,要想让一个操作耗费O(n)的时间,要先花费√n个操作对数列进行修改。

初始序列不同值,经过类似分析后,就可以放心的暴力啦。

#include
#include
#include
#include
using namespace std;
const int N = 1000000;
int pos[N],le[N],ri[N],cnt,n,a[N],tag[N],len;
void reset(int x)
{
	if(tag[x]==-1)return;
	else
	{
		for(int i=le[x];i<=ri[x];i++)
		a[i]=tag[x];
	}
	tag[x]=-1;
}
void change(int l,int r,int c)
{
	int ans=0;
	reset(pos[l]);
	for(int i=l;i<=min(ri[pos[l]],r);i++)
	{
		if(a[i]!=c)a[i]=c;
		else ans++;
	}
	if(pos[l]!=pos[r])
	{
		reset(pos[r]);
		for(int i=le[pos[r]];i<=r;i++)
		{
			if(a[i]!=c)a[i]=c;
			else ans++;
		}
	}
	for(int i=pos[l]+1;i<=pos[r]-1;i++)
	{
		if(tag[i]!=-1)
		{
			if(tag[i]!=c)tag[i]=c;
			else ans+=len;
		}
		else
		{
			for(int j=le[i];j<=ri[i];j++)
			{
				if(a[j]!=c)a[j]=c;
				else ans++;
				tag[i]=c;
			}
		}
	}
	printf("%d\n",ans);
}
int main()
{
	scanf("%d",&n);
    len=sqrt(n);
	memset(tag,-1,sizeof(tag));
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)pos[i]=(i-1)/len+1;
	for(int i=1;i<=pos[n];i++)
	{
		le[i]=(i-1)*len+1;
		ri[i]=min(n,i*len);
	}
	for(int i=1,l,r,c;i<=n;i++)
	{
		scanf("%d%d%d",&l,&r,&c);
		change(l,r,c);
	}
	return 0;
}

分块九:

给出一个长为n的数列,以及n个操作,操作涉及询问区间的最小众数。   不带修改,要是带呢?

 

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define mod 10007
#define pi acos(-1)
#define inf 0x7fffffff
#define ll long long
using namespace std;
ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,blo,id;
int v[100005],bl[100005];
int f[605][605];
mapmp;
int val[100005],cnt[100005];
vectorve[100005];
void pre(int x)
{   
    memset(cnt,0,sizeof(cnt));
    int mx=0,ans=0;
    for(int i=(x-1)*blo+1;i<=n;i++)
    {       
        cnt[v[i]]++;        
        int t=bl[i];
        if(cnt[v[i]]>mx||(cnt[v[i]]==mx&&val[v[i]]mx||(t==mx&&val[v[i]]mx||(t==mx&&val[v[i]]b)swap(a,b);
        printf("%d\n",val[query(a,b)]);
    }
    return 0;
}

 

 

 

      分块--好爽

转载于:https://www.cnblogs.com/yelir/p/11559947.html

你可能感兴趣的:(【分块入门1-9】--分块大法好)