根号分治做题记录

对于块长 \(B=\sqrt n\),对 \(\le B\)\(>B\) 采用其中一种使用暴力,另一种利用 \(\frac n B\le B\) 使用另一种策略。这种方法叫根号分治。
利用基本不等式等等,对 \(B\) 的取值进行平衡,是复杂度最优。

【例1】Train Maintenance

【例2】[POI2015]ODW

对步长 \(k\) 分类讨论。
\(k>B\) 时,跳的次数不会超过 \(n/B\),暴力跳,用倍增实现跳 \(k\) 条边。总 \(O(n\cdot n/B\log n)\)
\(k\le B\) 时,考虑对于所有 \(k\) 相同的一起考虑。这个东西显然是树上差分类似东西,所以记 \(w_{u,val}\)\(u\) 的满足 \(dep\bmod k=val\) 的前缀和则对一组 \((s,t,k)\) 的答案等于 \(w_{s,dep_s\bmod k}+w_{t,dep_t\bmod k}-w_{fa[lca(s,t)][0],dep_s\bmod k}-w_{fa[lca(s,t)][0],dep_t\bmod k}\),如果 \(lca\) 被算了2次就还要再减去 \(lca\) 的不赘述。这个总共 \(O(B\cdot n)\)

理论最优 \(B=\sqrt{n\log n}\),实测最优 \(B=20\),前者 TLE。标算使用长链剖分降低复杂度,然而我不会。

#include <bits/stdc++.h>
using namespace std;
const int N=5e4+5;
int n,num,a[N],b[N],c[N],res[N],dep[N],fa[N][17],cnt[225];
vector<int>G[N],want[N];
map<pair<int,int>,int>U;
struct J{
	int s,t,k,id;
}q[N];
inline int read(){
	register char ch=getchar();register int x=0;
	while(ch<'0'||ch>'9')ch=getchar();
	while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
	return x;
}
void dfs(int x,int p){
	dep[x]=dep[p]+1;
	fa[x][0]=p;
	for(int i=1;i<=15;i++)fa[x][i]=fa[fa[x][i-1]][i-1];
	for(int i=0;i<G[x].size();i++){
		int y=G[x][i];
		if(y^p)dfs(y,x);
	}
}
int glca(int u,int v){
	if(u==v)return u;
	if(dep[u]>dep[v])swap(u,v);
	for(int i=15;~i;i--)if(dep[fa[v][i]]>=dep[u])v=fa[v][i];
	if(u==v)return u;
	for(int i=15;~i;i--)if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}
void dfsk(int x,int p,int k){
	cnt[dep[x]%k]+=a[x];
	for(int i=0;i<want[x].size();i++)U[make_pair(x,want[x][i])]=cnt[want[x][i]];
	for(int i=0;i<G[x].size();i++){
		int y=G[x][i];
		if(y^p)dfsk(y,x,k);
	}
	cnt[dep[x]%k]-=a[x];
}
inline int tok(int u,int k){
	for(int i=15;(~i)&&u;i--)if((k>>i)&1)u=fa[u][i];
	return u;
}
int solve(int s,int t,int k){
	int lca=glca(s,t),sum=0;bool fl=0;
	for(;dep[s]>=dep[lca];s=tok(s,k)){sum+=a[s];if(s==lca)fl=1;}
	for(;dep[t]>=dep[lca];t=tok(t,k)){if(t==lca&&fl)break;sum+=a[t];}
	return sum;
}
void print(int x){
	if(x/10)print(x/10);
	putchar(x%10+48);
}
int main(){
	n=read();
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1,u,v;i<n;i++)u=read(),v=read(),G[u].push_back(v),G[v].push_back(u);
	for(int i=1;i<=n;i++)b[i]=read();
	for(int i=1;i<n;i++)c[i]=read();
	dfs(1,0);
	int B=20;
	for(int i=1;i<n;i++){
		if(c[i]<=B)q[++num]={b[i],b[i+1],c[i],i};
		else res[i]=solve(b[i],b[i+1],c[i]);
	}
	sort(q+1,q+num+1,[](J a,J b){return a.k<b.k;});
	for(int i=1,las=1;i<=num;i++){
		if(i==num||q[i].k!=q[i+1].k){
			U.clear();
			for(int j=1;j<=n;j++)want[j].clear();
			for(int j=las;j<=i;j++){
				want[q[j].s].push_back(dep[q[j].s]%q[i].k);
				want[q[j].t].push_back(dep[q[j].t]%q[i].k);
				int lca=fa[glca(q[j].s,q[j].t)][0];
				if(!lca)continue;
				want[lca].push_back(dep[q[j].s]%q[i].k);
				want[lca].push_back(dep[q[j].t]%q[i].k);
			}
			dfsk(1,0,q[i].k);
			for(int j=las;j<=i;j++){
				want[q[j].s].push_back(dep[q[j].s]%q[i].k);
				want[q[j].t].push_back(dep[q[j].t]%q[i].k);
				int lca2=glca(q[j].s,q[j].t),lca=fa[lca2][0];
				res[q[j].id]=U[make_pair(q[j].s,dep[q[j].s]%q[i].k)]+
					U[make_pair(q[j].t,dep[q[j].t]%q[i].k)]-
					U[make_pair(lca,dep[q[j].s]%q[i].k)]-
					U[make_pair(lca,dep[q[j].t]%q[i].k)];
				if(dep[lca2]%q[i].k==dep[q[j].s]%q[i].k&&
					dep[lca2]%q[i].k==dep[q[j].t]%q[i].k)
					res[q[j].id]-=a[lca2];
			}
			las=i+1;
		}
	}
	for(int i=1;i<n;i++)print(res[i]),puts("");
}

【例3】CF1654E Arithmetic Operations

每次可以把数列 \(a_n\) 选任一位置改成任意整数,最少几次变成等差数列?\(n,a_i\le 10^5\)

很显然题目可以化成最多多少个点共线,最后答案是 \(n-ans\)

写一下 \(a_i=ki+b\),移个项:\(b=a_i-ki\)。可以想到暴力思路是枚举公差 \(k\)\(O(n)\) 遍历 \(a_i\) 并将 \(b\) 放入桶,后来统计最多的 \(b\)

根据公差(值域为 \(M=\max a_i\))根号分治。设块长为 \(B\)

不妨设斜率(公差) \(k\ge 0\),因为只需要翻转 \(a\) 就是 \(k\le 0\)

  • \(d<B\) 时,上头的暴力思路可以使用。——\(O(nB)\)
  • \(d\ge B\) 时,需要探求一些性质。首先可以考虑 dp,设 \(f[i][k]\) 表示前 \(i\) 个数公差(斜率)为 \(k\)(显然是个整数)最多有多少个不修改,则 \(f[i][k]=\max\{f[j][k]\}\)。考虑降低转移复杂度,则需探究相邻两个不被修改的数之间满足什么性质。对于当前的 \(i\)\(j<i\),假若 \(i-j>M/B\),则 \(a_i-a_j=(i-j)k>M/B\cdot k\ge M\),这显然不可能,所以 \(j\) 只能 \(\ge M/B\)。dp 数组可以用 map 存,块长取 70 时很快。

submission

posted @ 2022-02-27 22:42  pengyule  阅读(61)  评论(0编辑  收藏  举报