【学习笔记】分块

前言:由于笔者分块水平极低,所以可能有各种各样的奇葩错误,看到了请直接指出来,不胜感激。

(这里基本都是较难的分块,前提芝士我就懒得写了)

FAQ

Q:分块是什么?

A:一种高端的暴力,具体来说就是将询问和修改的复杂度尽量平摊,一般来说这种问题都是询问(修改)复杂度较高(比如\(\Theta(n)\)),修改(询问)复杂度较低(比如\(\Theta(1)\)),如果使两者都达到\(\Theta(\sqrt n)\),问题就可以较高效的解决。

Q:只能分块做吗?

A:一般来说除了纯分块题都会有\(Poly log\)的做法...

正题

CF1178G

题意:

给定一棵有根树,每个点有两个数\(a_i,b_i \in [-5000,5000]\)定义其权值如下

\[\vert \sum_{w \in R(v)} a_w\vert \cdot \vert \sum_{w \in R(v)} b_w\vert \]

\(R(x)\)定义为\(x\)到根节点简单路径上的所有节点)

两种操作

1 v x:\(a_v +=x (x>0)\)

2 v:求子树\(v\)内的权值最大值

\(\text{limit} : n\le 100000\)

解法:

可以观察到\(b_i\)其实是固定的可以预处理出来

答案也就是\(max\{-min(v_i),v_i\}\)(负数也要处理)

然后我们发现其实就是动态求\(kx+b\)的最大值(\(b \rightarrow k, a\rightarrow x,a'b'\rightarrow b\)初始权值就是常数)

其实也就是在凸包上切出最大截距

那么我们可以按照\(dfn\)进行分块,每次中间的块打上偏移\(tag\)(凸包上位置往后移的tag),边角块暴力重构(初始值\(+=x*b\)),发现\(tag\)单增,于是不需要二分直接暴力往后移就好了。

代码比较清晰

//Love and Freedom.
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define inf 20021225
#define N 200010
#define BS 505
using namespace std;
int read()
{
	int s=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-') f=-1; ch=getchar();}
	while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
	return f*s;
}
struct edge{int to,lt;}e[N<<1];
int n,m,q;
int in[N],cnt,dfn[N],idfn[N],sz[N],a[N],b[N],A[N],B[N];
void add(int x,int y)
{
	e[++cnt].to=y; e[cnt].lt=in[x]; in[x]=cnt;
	e[++cnt].to=x; e[cnt].lt=in[y]; in[y]=cnt;
}
struct poi
{
	ll x,y;
	poi(){}
	poi(ll _x,ll _y){x=_x,y=_y;} 
	ll val(int _x){return x*_x+y;}
};
poi operator-(poi a,poi b){return poi(a.x-b.x,a.y-b.y);}
ll cross(poi a,poi b){return a.x*b.y-a.y*b.x;}
struct que
{
	poi q[BS]; int hd,tl;
	void init(){hd=1; tl=0;}
	void push(poi a){q[++tl]=a;}
	void addpoi(ll x,ll y)
	{
		poi ins=poi(x,y);
		while(hd<tl && cross(q[tl]-q[tl-1],ins-q[tl-1])>=0)	tl--; push(ins);
	}
	ll query(int x)
	{
		ll ans=-1e18;
		while(hd<tl && q[hd].val(x)<=q[hd+1].val(x))	hd++; if(hd<=tl)	ans=max(ans,q[hd].val(x));
		return ans;
	}
};
bool cmp(int x,int y){return B[x]<B[y];}
struct block
{
	que up,dn; int l,r,sz,num[BS],tag;
	ll query(){return max(up.query(tag),dn.query(tag));}
	void build()
	{
		up.init(); dn.init(); sz=r-l+1;
		for(int i=l;i<=r;i++)	num[i-l+1]=i;
		sort(num+1,num+sz+1,cmp);
		for(int i=1;i<=sz;i++)
			up.addpoi(B[num[i]],1ll*A[num[i]]*B[num[i]]);
		reverse(num+1,num+sz+1);
		for(int i=1;i<=sz;i++)
			dn.addpoi(-B[num[i]],-1ll*A[num[i]]*B[num[i]]);
	}
}kk[BS];
int tms;
void dfs(int x,int fr)
{
	sz[x]=1; dfn[x]=++tms; idfn[tms]=x;
	a[x]+=a[fr]; b[x]+=b[fr];
	for(int i=in[x];i;i=e[i].lt)
	{
		int y=e[i].to; if(y==fr)	continue;
		dfs(y,x); sz[x]+=sz[y];
	}
}
int bel[N];
void init()
{
	m=sqrt(n); int bk=(n+m-1)/m;
	for(int i=1;i<=bk;i++)
	{
		kk[i].l=(i-1)*m+1; kk[i].r=min(i*m,n);
		for(int j=kk[i].l;j<=kk[i].r;j++)
			bel[j]=i;
		kk[i].build();
	}
}
void modify(int l,int r,int v)
{
	if(bel[l]==bel[r])
	{
		for(int i=l;i<=r;i++)	A[i]+=v;
		kk[bel[l]].build(); return;
	}
	
	for(int i=l;i<=kk[bel[l]].r;i++)	A[i]+=v; kk[bel[l]].build();
	for(int i=kk[bel[r]].l;i<=r;i++)	A[i]+=v; kk[bel[r]].build();
	for(int i=bel[l]+1;i<bel[r];i++)	kk[i].tag+=v;
}
ll query(int l,int r)
{
	ll ans=-1e18;
	if(bel[l]==bel[r])
	{
		for(int i=l;i<=r;i++)
			ans=max(ans,1ll*abs(A[i]+kk[bel[l]].tag)*B[i]);
		return ans;
	}
	
	for(int i=l;i<=kk[bel[l]].r;i++)
		ans=max(ans,1ll*abs(A[i]+kk[bel[l]].tag)*B[i]);
	for(int i=kk[bel[r]].l;i<=r;i++)
		ans=max(ans,1ll*abs(A[i]+kk[bel[r]].tag)*B[i]);
	for(int i=bel[l]+1;i<bel[r];i++)
		ans=max(ans,kk[i].query());
	return ans;
}
int main()
{
	n=read(),q=read();
	for(int i=2;i<=n;i++)	add(read(),i);
	for(int i=1;i<=n;i++)	a[i]=read();
	for(int i=1;i<=n;i++)	b[i]=read();
	dfs(1,0);
	for(int i=1;i<=n;i++)	A[i]=a[idfn[i]],B[i]=abs(b[idfn[i]]);
	init();
	while(q--)
	{
		int ty=read(),x=read();
		if(ty==1)
		{
			int v=read();
			modify(dfn[x],dfn[x]+sz[x]-1,v);
		}
		else
		{
			printf("%I64d\n",query(dfn[x],dfn[x]+sz[x]-1));
		}
	}
	return 0;
}
[Ynoi2018] 末日时在做什么?有没有空?可以来拯救吗?

\(\color{red}{\text{warning}}\):这份代码没卡过去,只保证正确性。

题意:

区间加(正整数),区间最大子段和。

\(\text{limit}:n\leq 100000\)

解法:

先考虑全局来做,维护函数\(f(x)\)表示\(+x\)的答案,显然是个凸包(下凸)

类似上题维护\(tag\)然后我们发现我们无法块快速维护这个凸包

考虑一下这个凸包怎么搞

我们分治\((l,r)\)显然\((l,mid)\)\((mid,r)\)的凸包可以递归下去算

跨越\(mid\)的凸包并不好求,但我们可以想起单点修改的区间最大子段和怎么做,我们可以合并左区间的最大后缀和/右区间的最大前缀和(显然这两个也是凸的),合并凸包有奇技淫巧闵科夫斯基和(什么 你不会 那就戳这一篇博客学习一下)做到\(\Theta(n)\)合并。

然后写完了以后你就T飞啦 哈哈

具体优化什么的可以看这位神仙的博客来学一下

卡不过去可以用这个题验证正确性

(它怎么我前几天写的时候是权限题,今天就不是了??黑人问号脸.jpg)

//Love and Freedom.
#pragma GCC optimize(3)
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define N 100010
#define B 110
#define INF (1ll<<48)
using namespace std;
int read()
{
	int s=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-') f=-1; ch=getchar();}
	while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
	return f*s;
}
struct poi
{
	ll x,y;
	poi(){}
	poi(ll _x,ll _y){x=_x,y=_y;} 
};
poi operator+(poi a,poi b){return poi(a.x+b.x,a.y+b.y);}
poi operator-(poi a,poi b){return poi(a.x-b.x,a.y-b.y);}
ll cross(poi a,poi b){return a.x*b.y-a.y*b.x;}
struct data{ll l,r,ans,sum;};
data operator+(data a,data b)
{
	data ans;
	ans.l=max(a.l,a.sum+b.l);
	ans.r=max(b.r,b.sum+a.r);
	ans.ans=max(a.ans,max(b.ans,a.r+b.l));
	ans.sum=a.sum+b.sum;
	return ans;
}
void gethull(poi *st,int &top)
{
	int rem=top; top=1;
	for(int i=2;i<=rem;i++)
	{
		while(top>1 && cross(st[i]-st[top-1],st[top]-st[top-1])<=0)	top--;
		st[++top]=st[i];
	}
}
poi tmp[B+10]; int n,m;
void insert(poi a){tmp[a.x].y=max(tmp[a.x].y,a.y);}
struct block
{
	int l,r,sz; poi pre[B+10],suf[B+10],ans[B+10]; ll a[B+10];
	data val; ll tag; bool flag; int lsz; int s1,s2,s3,p1,p2,p3;
	int brute(int l,int r) // (l,r]
	{
		ll sum=0; for(int i=l+1;i<=r;i++)	sum+=a[i],ans[i]=poi(i-l,sum);
		for(int i=l+2;i<=r;i++)
		{
			sum=0; for(int j=0;i+j<=r;j++)
				sum+=a[i+j],ans[l+j+1].y=max(ans[l+j+1].y,sum);
		}
		s3=r-l; gethull(ans+l,s3); return s3;
	}
	ll getval(poi a){return a.y+a.x*tag;}
	int solve(int l,int r) // (l,r]
	{
		if(r-l<lsz)	return 0; if(r-l<17)	return brute(l,r);
		int mid=l+r>>1;
		int ls=solve(l,mid),rs=solve(mid,r);
		for(int i=1;i<=r-l;i++)	tmp[i]=poi(i,-INF);
		for(int i=l+1;i<=l+ls;i++)	insert(ans[i]);
		for(int i=mid+1;i<=mid+rs;i++)	insert(ans[i]);
		ll sum=0; s1=0; for(int i=mid+1;i<=r;i++)	s1++,sum+=a[i],pre[s1]=poi(s1,sum); gethull(pre,s1);
		sum=0; s2=0; for(int i=mid;i>l;i--)	s2++,sum+=a[i],suf[s2]=poi(s2,sum); gethull(suf,s2);
		int w1=1,w2=1; insert(pre[w1]+suf[w2]);
		while(w1!=s1&&w2!=s2)
		{
			if(cross(pre[w1+1]+suf[w2]-pre[w1]-suf[w2],pre[w1]+suf[w2+1]-pre[w1]-suf[w2])>=0)	w2++;
			else	w1++; insert(pre[w1]+suf[w2]);
		}
		while(w1!=s1)	w1++,insert(pre[w1]+suf[w2]);
		while(w2!=s2)	w2++,insert(pre[w1]+suf[w2]);
		gethull(tmp,s3=r-l); for(int i=1;i<=s3;i++)	ans[l+i]=tmp[i];
		return s3;
	}
	void moveon(poi *a,int &p,int &s)
	{
		while(p<s)	if(getval(a[p])<=getval(a[p+1]))	p++;
		else	return;
	}
	void recons()
	{
		if(tag)	for(int i=1;i<=sz;i++)	a[i]+=tag; tag=0;
		if(!flag){flag=1; for(int i=1;i<=sz;i++)	flag&=(a[i]>=0);}
		if(flag){val.sum=0; for(int i=1;i<=sz;i++)	val.sum+=a[i]; val.l=val.r=val.ans=val.sum; return;}
		lsz=ans[p3].x; solve(0,sz);
		ll sum=0; s1=0; for(int i=1;i<=sz;i++)	s1++,sum+=a[i],pre[s1]=poi(s1,sum); gethull(pre,s1);
		   sum=0; s2=0; for(int i=sz;i;i--)	s2++,sum+=a[i],suf[s2]=poi(s2,sum); gethull(suf,s2);
		p1=p2=p3=1; moveon(pre,p1,s1); moveon(suf,p2,s2); moveon(ans,p3,s3);
		flag&=(p1==s1)&&(p2==s2)&&(p3==s3);
		val=(data){max(pre[p1].y,0ll),max(suf[p2].y,0ll),max(ans[p3].y,0ll),sum};
	}
	void modify(int x)
	{
		if(flag){tag+=x; val.sum+=x*sz; val.l=val.r=val.ans=val.sum; return;}
		tag+=x; moveon(pre,p1,s1); moveon(suf,p2,s2); moveon(ans,p3,s3);
		val=(data){max(0ll,getval(pre[p1])),max(0ll,getval(suf[p2])),max(0ll,getval(ans[p3])),val.sum+x*sz};
	}
	data calc(int l,int r) // [l,r]
	{
		data wei=(data){0,0,0,0}; ll sum=0;
		for(int i=l;i<=r;i++)	sum+=a[i]+tag; wei.sum=sum; sum=0;
		for(int i=l;i<=r;i++)	sum+=a[i]+tag,wei.l=max(wei.l,sum); sum=0;
		for(int i=r;i>=l;i--)	sum+=a[i]+tag,wei.r=max(wei.r,sum); sum=0;
		for(int i=l;i<=r;i++)
		{
			sum+=a[i]+tag; if(sum<0)	sum=0;
			wei.ans=max(wei.ans,sum); 
		}
		return wei;
	}
	ll getans(int l,int r)
	{
		ll sum=0; ll ans=0;
		for(int i=l;i<=r;i++)
		{
			sum+=a[i]+tag; if(sum<0)	sum=0;
			ans=max(sum,ans);
		}
		return ans;
	}
}blo[(N/B)+1]; int bel[N];
void modify(int l,int r,int x)
{
	int id=bel[l],fr=blo[id].l;
	if(bel[l]==bel[r])
	{
		for(int i=l;i<=r;i++)
			blo[id].a[i-fr]+=x;
		blo[id].recons();
		return;
	}
	for(int i=l;i<=blo[id].r;i++)	blo[id].a[i-fr]+=x;	blo[id].recons(); id=bel[r],fr=blo[id].l;
	for(int i=blo[id].l+1;i<=r;i++)	blo[id].a[i-fr]+=x; blo[id].recons();
	for(int i=bel[l]+1;i<bel[r];i++)	blo[i].modify(x);
}
ll query(int l,int r)
{
	int id=bel[l],fr=blo[id].l;
	if(bel[l]==bel[r])	return blo[id].getans(l-fr,r-fr);
	data qwq=blo[id].calc(l-fr,blo[id].r-fr);
	for(int i=bel[l]+1;i<bel[r];i++)	qwq=qwq+blo[i].val;
	qwq=qwq+blo[bel[r]].calc(1,r-blo[bel[r]].l);
	return qwq.ans;
}
char ch[10];
int main()
{
	n=read(),m=read();
	for(int i=0,id=0;i<=n;i+=B,id++)
	{
		for(int j=1;j<=B&&i+j<=n;j++)	blo[id].a[j]=read(),bel[i+j]=id;
		blo[id].sz=min(n-i,B); blo[id].l=i; blo[id].r=i+B; blo[id].recons();
	}
	while(m--)
	{
		scanf("%s",ch+1); int l=read(),r=read();
		if(ch[1]=='A')	modify(l,r,read());
		else	printf("%lld\n",query(l,r));
	}
	return 0;
}
区间逆序对

这个简单(

解法:

维护点到块的前缀/后缀逆序对个数,然后边角暴力就好了

//Love and Freedom.
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define inf 20021225
#define N 51000
#define B 200
using namespace std;
int read()
{
	int s=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-') f=-1; ch=getchar();}
	while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
	return f*s;
}
int tmp[N];
int solve(int *a,int n)
{
	if(n<=1)	return 0;
	int s1=n>>1,s2=n-s1,ans=solve(a,s1)+solve(a+s1,s2),l=0,r=0,sz=0;
	while(l<s1&&r<s2)
	{
		if(a[l]>a[s1+r])	tmp[sz++]=a[r+s1],r++;
		else	tmp[sz++]=a[l++],ans+=r;
	}
	while(l<s1)	tmp[sz++]=a[l++],ans+=s2;
	while(r<s2)	tmp[sz++]=a[s1+r],r++; 
	for(int i=0;i<sz;i++)	a[i]=tmp[i];
	return ans;
}
int suf[N][(N/B)+1],pre[N][(N/B)+1]; int a[N+10],n,v[N+10];
int cnt[N]; int bel[N],lb[(N/B)+1],rb[(N/B)+1],blo[(N/B)+1],sz[(N/B)+1],bc,rem[N+10];
int main()
{
	n=read(); for(int i=1;i<=n;i++)	v[i]=a[i]=read();
	sort(v+1,v+n+1); int nn=unique(v+1,v+n+1)-v;
	for(int i=1;i<=n;i++)	rem[i]=a[i]=lower_bound(v+1,v+nn,a[i])-v;
	for(int i=1,id=1;i<=n;i+=B,id++)
	{
		for(int j=0;j<B;j++)	cnt[a[i+j]]++,bel[i+j]=id;
		for(int j=1;j<=nn;j++)	cnt[i]+=cnt[i-1];
		for(int j=1;j<i;j++)	pre[j][id]=pre[j-1][id]+cnt[a[j]-1];
		lb[id]=i; rb[id]=min(n,i+B-1); sz[id]=rb[id]-lb[id]+1; blo[id]=solve(a+i,sz[id]);
		bc=id; for(int j=1;j<=nn;j++)	cnt[j]=0;
	}
	for(int i=bc;i;i--)
	{
		int l=lb[i],r=rb[i];
		for(int j=l;j<=r;j++)	cnt[a[j]]++;
		for(int j=1;j<=nn;j++)	cnt[j]+=cnt[j-1];
		for(int j=r+1;j<=n;j++)	suf[j][i]=suf[j+1][i]+(sz[i]-cnt[a[j]]);
		for(int j=1;j<=nn;j++)	cnt[j]=0;
	}
	int m=read(),lastans=0;
	while(m--)
	{
		int l=read()^lastans,r=read()^lastans;
		if(bel[l]==bel[r])
		{
			for(int i=l;i<=r;i++)	a[i]=rem[i];
			printf("%d\n",lastans=solve(a+l,r-l+1));
			continue;
		}
		int lid=bel[l],rid=bel[r],tot=0;
		for(int i=l;i<=rb[lid];i++)	a[tot++]=rem[i];
		for(int i=lb[rid];i<=r;i++)	a[tot++]=rem[i];
		int ans=solve(a,tot);
		for(int i=lid+1;i<rid;i++)	ans+=pre[r][i]-pre[l-1][i]+suf[l][i]-suf[r+1][i]+blo[i];
		printf("%d\n",lastans=ans);
	}
	return 0;
}

md我好像3个题写了3天??8是很懂(

\(\color{red}{\text{warning}}\):剩下的题都没写 只是看懂的 所以写错的可能性极大

二次离线莫队

考虑\([l,r]\)\([l',r']\)拓展

对于右边拓展\(r\)答案增量就是\(query(1,r-1)-query(1,l-1)\)

同样左端点用后缀就可以了

也就可以转化成查询\([1,r]\)\(>s[r]\)的个数和查询\([1,r]\)\(>s[a,b]\)的个数,这两个可以\(\Theta(1)\)回答,所以总复杂度\(\Theta(n\sqrt n)\)

前提:满足可减性

第14分块(Hard Ver.)

题意:

\(n\)个数\(a_i\)

每次给出一个区间\([l,r]\)查询区间内多少二元组\((i,j)\)满足\(a_i\)\(a_j\)倍数

\(\text{limit}: n,a_i \le 100000\)

解法:

二次离线莫队,转化成下面的问题

查询\([1,r]\)中是\(s[r]\)/\(s[a,b]\)的约数/倍数的个数

倍数都可以直接枚举约数得到,单个约数可以预处理,就只剩查一整个块的约数了。

考虑对约数根号分治,\(>\sqrt{n}\)的可以扫描线枚举倍数,\(\le \sqrt n\)的可以先枚举,然后搞前缀和直接算,考虑到这个玩意的区间数只有\(\sqrt n\)所以是\(\Theta (\sqrt n ^3)\)还是\(\Theta(n \sqrt n)\)

天天爱跑步·改

啊 我懒得写了 看这个吧

img

BZOJ5145
CF896E
UOJ 33
UOJ435
UOJ337

以上为咕咕名单

posted @ 2020-01-18 10:36  寒雨微凝  阅读(77)  评论(0)    收藏  举报