根号分治做题记录
对于块长 ,对 和 采用其中一种使用暴力,另一种利用 使用另一种策略。这种方法叫根号分治。
利用基本不等式等等,对 的取值进行平衡,是复杂度最优。
【例1】Train Maintenance
【例2】[POI2015]ODW
对步长 分类讨论。
当 时,跳的次数不会超过 ,暴力跳,用倍增实现跳 条边。总
当 时,考虑对于所有 相同的一起考虑。这个东西显然是树上差分类似东西,所以记 为 的满足 的前缀和则对一组 的答案等于 ,如果 被算了2次就还要再减去 的不赘述。这个总共 。
理论最优 ,实测最优 ,前者 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
每次可以把数列 选任一位置改成任意整数,最少几次变成等差数列?
很显然题目可以化成最多多少个点共线,最后答案是 。
写一下 ,移个项:。可以想到暴力思路是枚举公差 , 遍历 并将 放入桶,后来统计最多的 。
根据公差(值域为 )根号分治。设块长为 。
不妨设斜率(公差) ,因为只需要翻转 就是 。
- 当 时,上头的暴力思路可以使用。——。
- 当 时,需要探求一些性质。首先可以考虑 dp,设 表示前 个数公差(斜率)为 (显然是个整数)最多有多少个不修改,则 。考虑降低转移复杂度,则需探究相邻两个不被修改的数之间满足什么性质。对于当前的 和 ,假若 ,则 ,这显然不可能,所以 只能 。dp 数组可以用 map 存,块长取 70 时很快。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现