大厨的分块题

( ext{Chef and Churu})

解法

拼接两个算法分块

这道题显然有两种信息维护方式:

  • 维护 (A) 的前缀和,对于单个函数 (f(i)),直接查询对应的 (A)
  • 对于 (A_i) 的修改,修改区间包含 (i) 的函数 (f(i))

第二种维护可以将函数分块,维护每个块内所有 (A_i) 的贡献次数,差分实现可以做到 (mathcal O(ncdot frac{n}{B}))(具体实现请见 getNum() 函数)预处理。但无法处理散块的情况,而散块满足大小在 (B) 范围,所以可以暴力更新函数。

如何维护 (A) 的前缀和?可以用数据结构进行维护,但询问散块复杂度就上升到 (mathcal O(qBcdot log n))。其实只需要维护每块的前缀和和块与块之间的前缀和,这样单次修改是是 (mathcal O(B+frac{n}{B})) 的,询问降到了 (mathcal O(qB))。总体复杂度是 (mathcal O(qcdot(B+frac{n}{B}))),所以 (B)(sqrt n) 即可。

需要注意的是,答案范围 (10^5 imes 10^5 imes 10^9=10^{19}),要开 unsigned long long

代码

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f|=(s=='-');
	while(s>='0' and s<='9')
		x=(x<<1)+(x<<3)+(s^48),
		s=getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-'),write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
} 

#include <cmath>
#include <iostream>
using namespace std;
typedef unsigned long long ll;

const int maxn=1e5+5;

int n,a[maxn],bl[maxn];
int rk[maxn],cnt,B,L[maxn];
int R[maxn];
struct Block {
	int l,r,num[maxn];
	ll sum[400],S,ans;
} s[400];

void getSum(int x) {
	ll tmp=0;
	for(int i=s[x].l;i<=s[x].r;++i) {
		tmp+=a[i];
		s[x].sum[rk[i]]=tmp;
	}
	s[x].sum[B]=tmp;
	for(int i=x;i<=cnt;++i)	
		s[i].S=s[i-1].S+s[i].sum[B];
}

ll Query(int x,int y) {
	if(bl[x]^bl[y]) {
		if(bl[x]+1==bl[y])
			return s[bl[y]].sum[rk[y]]-s[bl[x]].sum[rk[x]-1]+s[bl[x]].sum[B];
		return s[bl[y]].sum[rk[y]]+s[bl[y]-1].S-s[bl[x]-1].S-s[bl[x]].sum[rk[x]-1];
	}
	return s[bl[x]].sum[rk[y]]-s[bl[x]].sum[rk[x]-1];
}

void getNum(int x) {
	for(int i=s[x].l;i<=s[x].r;++i) {
		++s[x].num[L[i]],--s[x].num[R[i]+1];
		s[x].ans+=Query(L[i],R[i]);
	}
	for(int i=1;i<=n;++i)
		s[x].num[i]+=s[x].num[i-1];
}

int main() {
	n=read(9); B=sqrt(n);
	for(int i=1;i<=n;++i)
		a[i]=read(9);
	for(int i=1;i<=n;++i) {
		L[i]=read(9),R[i]=read(9);
		bl[i]=(i-1)/B+1;
		rk[i]=(i-1)%B+1;
	}
	cnt=bl[n];
	for(int i=1;i<=cnt;++i) {
		s[i].l=s[i-1].r+1;
		s[i].r=s[i].l+B-1;
	}
	s[cnt].r=min(s[cnt].r,n);
	for(int i=1;i<=cnt;++i)
		getSum(i);
	for(int i=1;i<=cnt;++i)
		getNum(i);
	int op,x; ll y,dec,Ans;
	for(int m=read(9);m;--m) {
		op=read(9),x=read(9),y=read(9);
		if(op==1) {
			dec=y-a[x],a[x]=y;
			getSum(bl[x]);
			for(int i=1;i<=cnt;++i)
				s[i].ans+=dec*s[i].num[x];
		}
		else {
			Ans=0;
			if(bl[x]^bl[y]) {
				for(int i=x;i<=s[bl[x]].r;++i)
					Ans+=Query(L[i],R[i]);
				for(int i=s[bl[y]].l;i<=y;++i)
					Ans+=Query(L[i],R[i]);
				for(int i=bl[x]+1;i<bl[y];++i)
					Ans+=s[i].ans;
			}
			else {
				for(int i=x;i<=y;++i)
					Ans+=Query(L[i],R[i]);
			}
			printf("%llu
",Ans);
		}
	}
	return 0;
}

( ext{Chef and Problems})

解法

好妙啊,预处理 (pre_{i,j},suf_{i,j}) 表示第 (i) 种数在 (j) 块之后/之前(包括第 (j) 块)的最早/最晚出现位置。根据这个可以 (mathtt{dp})(f_{i,j}) 表示 (i,j) 块之间的答案。

询问时讨论 (l,r) 分别是否在块 ([bl_l+1,bl_r-1]) 之间即可。


( ext{Update on 2021.8.17})

对于序列分块。另外 歴史の研究 也可以用类似的方法分块,存一下块的左右端点可以去掉 (log)

这样的问题可以考虑此种分块:

  • 可以预处理/快速得出一段连续块之间的答案。
  • 散块的答案可以在 (mathcal O(B)/ mathcal Oleft(frac{n}{B} ight )) 之内拼接。

好像没啥用,我还是不会

代码

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f|=(s=='-');
	while(s>='0' and s<='9')
		x=(x<<1)+(x<<3)+(s^48),
		s=getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-'),write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
} 

#include <cmath>
#include <cstring>
#include <iostream>
using namespace std;

const int maxn=1e5+5,inf=1e9;

int n,m,q,a[maxn],bl[maxn],B;
int pre[maxn][400],suf[maxn][400];
int f[400][400],Left[maxn];

int main() {
	memset(pre,0x3f,sizeof pre);
	n=read(9),m=read(9),q=read(9);
	B=sqrt(n); 
	for(int i=1;i<=n;++i)
		a[i]=read(9),
		bl[i]=(i-1)/B+1,
		suf[a[i]][bl[i]]=i;
	for(int i=n;i>=1;--i)
		pre[a[i]][bl[i]]=i;
	for(int i=1;i<=m;++i) {
		for(int j=bl[n];j>=1;--j)
			pre[a[i]][j]=min(pre[a[i]][j],pre[a[i]][j+1]);
		for(int j=1;j<=bl[n];++j)
			suf[a[i]][j]=max(suf[a[i]][j],suf[a[i]][j-1]);
	}
	for(int i=bl[n];i>=1;--i)
		for(int j=i;j<=bl[n];++j) {
			f[i][j]=max(f[i+1][j],f[i][j-1]);
			for(int k=(i-1)*B+1;k<=min(i*B,n);++k)
				f[i][j]=max(f[i][j],suf[a[k]][j]-k);
		}
	int l,r,ans;
	while(q--) {
		l=read(9),r=read(9);
		ans=f[bl[l]+1][bl[r]-1];
		for(int i=l;i<=min(bl[l]*B,n);++i)
			ans=max(ans,suf[a[i]][bl[r]-1]-i);
		for(int i=min(bl[l]*B,n);i>=l;--i)
			Left[a[i]]=i;
		for(int i=(bl[r]-1)*B;i<=r;++i) {
			ans=max(ans,i-pre[a[i]][bl[l]+1]);
			if(Left[a[i]])
				ans=max(ans,i-Left[a[i]]);
		}
		for(int i=min(bl[l]*B,n);i>=l;--i)
			Left[a[i]]=0;
		print(ans,'
');
	}
	return 0;
}

( ext{Children Trips})

题目大意

给定 (n) 个点的树,每条边长度是 (1)(2)。有 (m) 个询问,每次给出 (x,y,z),你需要算出 (x ightarrow y) 的路径最少能被划分成多少长度不超过 (z) 的段。

(n,mle 10^5)

解法

考虑计算 (x,y)( m lca),分别跳上去计算答案,最后几步可以拼起来。

分块可以利用 (z) 的性质:

  • (z>sqrt n)。如果每次跳 (z) 步,那么至多跳 (sqrt n) 次。由此可以直接模拟向上跳的过程,每次花费 (mathcal O(log n)) 跳至多 (z) 步,所以是 (mathcal O(msqrt ncdot log n)) 的。
  • (zle sqrt n)(z) 的种数不超过 (sqrt n)。考虑加速向上跳的过程:预处理一个倍增的数组 (up_{i,j}),初始化 (up_{i,0})(i) 向上跳 (2^0)(z) 到的点。复杂度 (mathcal O(nsqrt ncdot log n+mlog n))

有个需要注意的点就是不能跳到 ( m lca),最多只能跳到它下面。这是为了处理最后几步的情况。

代码

#include <cstdio>
#define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while((s=getchar())>'9' or s<'0')
		f|=(s=='-');
	while(s>='0' and s<='9')
		x=(x<<1)+(x<<3)+(s^48),
		s=getchar();
	return f?-x:x;
}

template <class T>
inline void write(const T x) {
	if(x<0) {
		putchar('-'),write(-x);
		return;
	}
	if(x>9) write(x/10);
	putchar(x%10^48);
} 

#include <cmath>
#include <iostream>
#include <algorithm>
using namespace std;
typedef pair <int,int> pii;

const int maxn=1e5+5;

int n,m,head[maxn],cnt,M;
int ans[maxn],s_cnt,b_cnt;
int f[maxn][20],dep[maxn];
int up[maxn][20],dis[maxn];
struct node {
	int x,y,p,id;
	
	bool operator < (const node &t) const {
		return p<t.p;
	}
} S[maxn],B[maxn];
struct Edge {
	int nxt,to,w;
} e[maxn<<1];

void addEdge(int u,int v,int w) {
	e[++cnt].w=w;
	e[cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
	e[++cnt].w=w;
	e[cnt].to=u;
	e[cnt].nxt=head[v];
	head[v]=cnt;
}

int Lca(int x,int y) {
	if(dep[x]<dep[y])	
		swap(x,y);
	for(int i=18;i>=0;--i)
		if(dep[f[x][i]]>=dep[y])
			x=f[x][i];
	if(x==y) return x;
	for(int i=18;i>=0;--i)
		if(f[x][i]^f[y][i]) {
			x=f[x][i];
			y=f[y][i];
		}
	return f[x][0];
}

int jump(int x,int p) {
	int rec=x;
	for(int i=18;i>=0;--i)
		if(dis[rec]-dis[f[x][i]]<=p)
			x=f[x][i];
	return x;
}

pii brute(int x,int y,int p) {
	pii r=make_pair(0,0);
	while(x^y) {
		int z=jump(x,p);
		if(dep[z]>dep[y])	
			++r.first,
			x=z;
		else 
			r.second+=dis[x]-dis[y],x=y;
	}
	return r;
}

pii Jump(int x,int y,int p) {
	pii r=make_pair(0,0);
	for(int i=18;i>=0;--i)
		if(dep[up[x][i]]>dep[y])
			x=up[x][i],
			r.first+=(1<<i);
	r.second=dis[x]-dis[y];
	return r;
}

void init(int p) {
	for(int i=1;i<=n;++i)
		up[i][0]=jump(i,p);
	for(int j=1;j<=18;++j)
		for(int i=1;i<=n;++i)
			up[i][j]=up[up[i][j-1]][j-1];
}

void calc(const node &t) {
	if(t.x==t.y) {
		ans[t.id]=0;
		return;
	}
	int lca=Lca(t.x,t.y);
	pii r1,r2;
	if(t.p>M) {
		r1=brute(t.x,lca,t.p);
		r2=brute(t.y,lca,t.p);
	}
	else {
		r1=Jump(t.x,lca,t.p);
		r2=Jump(t.y,lca,t.p);
	}
	ans[t.id]=r1.first+r2.first;
	if(r1.second+r2.second<=t.p)
		++ans[t.id];
	else ans[t.id]+=2;
}

void b_solve() {
	for(int i=1;i<=b_cnt;++i) 
		calc(B[i]);
}

void s_solve() {
	sort(S+1,S+s_cnt+1);
	for(int i=1;i<=s_cnt;++i) {
		if(i==1 or (S[i].p^S[i-1].p))
			init(S[i].p);
		calc(S[i]);
	}
}

void dfs(int u,int fa) {
	f[u][0]=fa,dep[u]=dep[fa]+1;
	for(int i=1;i<=18;++i)
		f[u][i]=f[f[u][i-1]][i-1];
	for(int i=head[u];i;i=e[i].nxt)
		if(e[i].to^fa)
			dis[e[i].to]=dis[u]+e[i].w,
			dfs(e[i].to,u);
}

int main() {
	n=read(9); M=sqrt(n);
	int u,v,w;
	for(int i=1;i<n;++i) {
		u=read(9),v=read(9),w=read(9);
		addEdge(u,v,w);
	}
	m=read(9); dfs(1,0);
	for(int i=1;i<=m;++i) {
		u=read(9),v=read(9),w=read(9);
		if(w>M) 
			B[++b_cnt]=(node){u,v,w,i};
		else
			S[++s_cnt]=(node){u,v,w,i};
	}
	b_solve(),s_solve();
	for(int i=1;i<=m;++i)
		print(ans[i],'
');
	return 0;
}
原文地址:https://www.cnblogs.com/AWhiteWall/p/15139575.html