根号分治做题记录

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

【例1】Train Maintenance

【例2】[POI2015]ODW

对步长 k 分类讨论。
k>B 时,跳的次数不会超过 n/B,暴力跳,用倍增实现跳 k 条边。总 O(nn/Blogn)
kB 时,考虑对于所有 k 相同的一起考虑。这个东西显然是树上差分类似东西,所以记 wu,valu 的满足 depmodk=val 的前缀和则对一组 (s,t,k) 的答案等于 ws,depsmodk+wt,deptmodkwfa[lca(s,t)][0],depsmodkwfa[lca(s,t)][0],deptmodk,如果 lca 被算了2次就还要再减去 lca 的不赘述。这个总共 O(Bn)

理论最优 B=nlogn,实测最优 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

每次可以把数列 an 选任一位置改成任意整数,最少几次变成等差数列?n,ai105

很显然题目可以化成最多多少个点共线,最后答案是 nans

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

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

不妨设斜率(公差) k0,因为只需要翻转 a 就是 k0

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

submission

posted @   pengyule  阅读(63)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示