根号算法训练记录 2025.2
根号算法其实和暴力很像,但是会按照一定的范围对操作分类讨论或更改顺序,以达到更优的时间复杂度,主要分为根号分治,分块和莫队,通俗来说,就是一种优雅的暴力
根号分治
其实准确来说,根号分治并不是一个分治算法,而是按照数据规模进行预处理和询问的复杂度平衡
对于题目中可以转化为和
一般情况下,
所以,其实根号分治更像一个技巧,或是平常说的数据点分治
Tree Master
https://www.gxyzoj.com/d/hzoj/p/4498
题目中要求的是让两个同深度的点不断向上跳,跟其复杂度相关的第一个量就是整棵树的深度
当深度很大时,每一层的节点数就会很少,可以对每对节点直接预处理
所以可以根据每层节点数进行分治,如果当前层节点数多于
否则记忆化,统计时记录已知的点对,总共至多记
所以总时间复杂度是
点击查看代码
#include<cstdio>
#include<cmath>
#define ll long long
using namespace std;
int n,q,a[100005],f[100005],len;
int id[100005],cnt[100005],dep[100005];
ll mp[100005][320];
ll dfs(int x,int y)
{
if(x==0&&y==0) return 0;
if(cnt[dep[x]]<=len)
{
if(!mp[x][id[y]])
mp[x][id[y]]=dfs(f[x],f[y])+1ll*a[x]*a[y];
return mp[x][id[y]];
}
else return dfs(f[x],f[y])+1ll*a[x]*a[y];
}
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
dep[1]=1;
for(int i=2;i<=n;i++)
{
scanf("%d",&f[i]);
dep[i]=dep[f[i]]+1;
id[i]=++cnt[dep[i]];
}
len=sqrt(n);
while(q--)
{
int x,y;
scanf("%d%d",&x,&y);
printf("%lld\n",dfs(x,y));
}
return 0;
}
[COTS 2024] 双双决斗 Dvoboj
https://www.gxyzoj.com/d/hzoj/p/LG10680
先考虑不带修的情况,因为是求
如果用 ST 表预处理出所有结果,在不带修的情况下,可以
接下来加入修改,虽然是单点修改,但是要在ST表上更新,就需要将整个 ST 表重新填,每一次都是
如何让更改的涉及范围变小?
注意到,这个 ST 表都要更改,是因为这个单点修改在
所以考虑只预处理较小的
那么时间复杂度就是
点击查看代码
#include<cstdio>
#include<cmath>
using namespace std;
int n,q,a[200005],st[200005][20],len,b,t[200005][20];
void build_st()
{
for(int i=1;i<=n;i++)
{
st[i][0]=a[i];
}
for(int j=1;(1<<j)<=b;j++)
{
for(int i=1;i+(1<<j)-1<=n;i++)
{
st[i][j]=abs(st[i][j-1]-st[i+(1<<(j-1))][j-1]);
}
}
}
int main()
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
b=sqrt(n);
for(int i=0;i<=20;i++)
{
if((1<<i)<=b) len=i;
}
build_st();
while(q--)
{
int opt,x,y;
scanf("%d%d%d",&opt,&x,&y);
if(opt==1)
{
st[x][0]=a[x]=y;
for(int j=1;(1<<j)<=b;j++)
{
for(int i=x-(1<<j)+1;i+(1<<j)-1<=n&&i<=x;i++)
{
st[i][j]=abs(st[i][j-1]-st[i+(1<<(j-1))][j-1]);
}
}
}
else
{
if(y<=len)
{
printf("%d\n",st[x][y]);
}
else
{
int tmp=1<<(y-len);
for(int i=1,j=x;i<=tmp;i++,j+=(1<<len))
{
t[i][0]=st[j][len];
}
for(int j=1;(1<<j)<=tmp;j++)
{
for(int i=1;i+(1<<j)-1<=tmp;i++)
{
t[i][j]=abs(t[i][j-1]-t[i+(1<<(j-1))][j-1]);
}
}
printf("%d\n",t[1][y-len]);
}
}
}
return 0;
}
Till I Collapse
https://www.gxyzoj.com/d/hzoj/p/4501
想到了一部分吧
因为只有
所以可以枚举段数为
但是当
点击查看代码
#include<cstdio>
#include<cmath>
using namespace std;
int n,a[100005],cnt[100005];
int get(int x)
{
int tot=1,sum=0;
for(int i=1;i<=n;i++) cnt[i]=0;
for(int i=1;i<=n;i++)
{
if(cnt[a[i]]!=tot) cnt[a[i]]=tot,sum++;
if(sum>x) tot++,sum=1,cnt[a[i]]=tot;
// printf("%d %d %d %d %d\n",x,a[i],tot,cnt[a[i]]);
// printf("%d ",tot);
}
return tot;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
}
int len=sqrt(n);
for(int i=1;i<=n;i++)
{
// printf("%d ",i);
if(i<=len)
{
printf("%d ",get(i));
continue;
}
int l=i,r=n,ans=get(i);
while(l<r)
{
int mid=(l+r+1)>>1;
if(get(mid)<ans) r=mid-1;
else l=mid;
}
// printf("%d %d\n",i,l);
for(int j=i;j<=l;j++) printf("%d ",ans);
i=l;
}
return 0;
}
Mr. Kitayuta's Colorful Graph
https://www.gxyzoj.com/d/hzoj/p/4502
看到多少种颜色,联通,考虑将每一种颜色的边分别用并查集缩点,此时,只要枚举
但是这样复杂度很高,考虑预处理一些颜色
我们可以提前处理涉及点数小于等于
此时,至多剩
注意因为点数很多,要使用map,注意常数
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,m,len,f[200005],tot,tp[200005];
map<pair<int,int>,int> mp,id;
vector<int> v,p[100005];
int find(int x)
{
if(f[x]!=x) f[x]=find(f[x]);
return f[x];
}
struct edge{
int x,y,c;
}e[100005];
int read()
{
int x=0;
char ch=getchar();
while(ch<48||ch>57) ch=getchar();
while(ch>=48&&ch<=57)
{
x=x*10+ch-48;
ch=getchar();
}
return x;
}
void write(int x)
{
if(x>9)
{
write(x/10);
}
putchar(x%10+48);
return;
}
int main()
{
n=read(),m=read();
int fl;
for(int i=1;i<=m;i++)
{
int x,y,c;
x=read(),y=read(),c=read();
pair<int,int> a=make_pair(x,c),b=make_pair(y,c);
if(!id.count(a))
{
p[c].push_back(x);
id[a]=++tot;
}
if(!id.count(b))
{
p[c].push_back(y);
id[b]=++tot;
}
e[i]=(edge){x,y,c};
}
for(int i=1;i<=tot;i++) f[i]=i;
for(int i=1;i<=m;i++)
{
int x=id[make_pair(e[i].x,e[i].c)];
int y=id[make_pair(e[i].y,e[i].c)];
f[find(x)]=find(y);
}
len=sqrt(n);
for(int i=1;i<=m;i++)
{
if(!p[i].size()) continue;
if(p[i].size()<=len)
{
for(int j=0;j<p[i].size();j++)
{
tp[j]=id[make_pair(p[i][j],i)];
}
for(int j=0;j<p[i].size();j++)
{
for(int k=j+1;k<p[i].size();k++)
{
if(find(tp[j])==find(tp[k]))
{
mp[make_pair(p[i][j],p[i][k])]++;
mp[make_pair(p[i][k],p[i][j])]++;
}
}
}
}
else v.push_back(i);
}
int q;
q=read();
while(q--)
{
int x,y;
x=read(),y=read();
int ans=0;
if(mp.count(make_pair(x,y))) ans=mp[make_pair(x,y)];
// printf("%d\n",ans);
for(int i=0;i<v.size();i++)
{
int c=v[i];
if(!id.count(make_pair(x,c))) continue;
if(!id.count(make_pair(y,c))) continue;
if(find(id[make_pair(x,c)])==find(id[make_pair(y,c)]))
ans++;
// printf("%d %d\n",c,ans);
}
write(ans);
putchar('\n');
}
return 0;
}
分块
ycz的妹子
https://www.gxyzoj.com/d/hzoj/p/LG4879
其他操作暴力就行了,关键在于 D 操作,分块统计人数,块长设成
[THUPC 2017] 钦妹的玩具商店
https://www.gxyzoj.com/d/hzoj/p/4497
抽象
因为是求每个小朋友的最大愉悦值,一定是要跑背包的。
暴力的一种思路是正着跑一遍,再逆着跑一遍,然后将
但是这样的合并是
还有一种暴力的方法是对每一组
因为合并的时间复杂度太大,必须舍弃,第二种方法每一问重新计算,重复的运算很多。
所以可以预处理出不包含一些
记
转移时,暴力添加多余部分即可,块长可以取
多重背包部分,可以使用单调优化,二进制优化会多个
注意,题目中愉悦度之和是取模后的!
点击查看代码
#include<cstdio>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
const int mod=1e8+7;
int T,n,m,Q,c[1005],lim[1005],blen,t;
int st[40],ed[40],pos[1005],cnt,id[1005];
int head,tail;
ll f[35][35][1005],d[1005],dp[1005],v[1005];
struct node{
int x;
ll v;
}q[1005];
void add(int ax,int ay,int bx,int by,int l,int r)
{
// printf("%d %d %d %d\n",ax,ay,bx,by);
for(int i=0;i<=m;i++)
{
f[ax][ay][i]=f[bx][by][i];
}
for(int i=l;i<=r;i++)
{
for(int j=0;j<c[i];j++)
{
head=1,tail=cnt=0;
for(int k=j;k<=m;k+=c[i])
{
d[++cnt]=f[ax][ay][k];
id[cnt]=k;
}
for(int k=1;k<=cnt;k++)
{
while(head<=tail&&q[head].x<k-lim[i]) head++;
while(head<=tail&&q[tail].v<=d[k]-k*v[i]) tail--;
q[++tail]=(node){k,d[k]-k*v[i]};
f[ax][ay][id[k]]=max(f[ax][ay][id[k]],q[head].v+k*v[i]);
}
}
}
}
void add1(int i)
{
for(int j=0;j<c[i];j++)
{
head=1,tail=cnt=0;
for(int k=j;k<=m;k+=c[i])
{
d[++cnt]=dp[k];
id[cnt]=k;
}
for(int k=1;k<=cnt;k++)
{
while(head<=tail&&q[head].x<k-lim[i]) head++;
while(head<=tail&&q[tail].v<=d[k]-k*v[i]) tail--;
q[++tail]=(node){k,d[k]-k*v[i]};
dp[id[k]]=max(dp[id[k]],q[head].v+k*v[i]);
}
}
}
int main()
{
// freopen("1.txt","r",stdin);
scanf("%d",&T);
while(T--)
{
scanf("%d%d%d",&n,&m,&Q);
for(int i=1;i<=n;i++) scanf("%d",&c[i]);
for(int i=1;i<=n;i++) scanf("%lld",&v[i]);
for(int i=1;i<=n;i++) scanf("%d",&lim[i]);
blen=sqrt(n),t=(n+blen-1)/blen;
for(int i=1;i<=t;i++)
{
st[i]=(i-1)*blen+1,ed[i]=blen*i;
}
ed[t]=n;
for(int i=1;i<=t;i++)
{
for(int j=st[i];j<=ed[i];j++) pos[j]=i;
}
for(int i=0;i<=t+1;i++)
{
for(int j=0;j<=t+1;j++)
{
for(int k=0;k<=m;k++)
{
f[i][j][k]=0;
}
}
}
for(int i=t;i>0;i--)
{
add(0,i,0,i+1,st[i],ed[i]);
}
for(int len=t;len>0;len--)
{
for(int i=1;i+len<=t+1;i++)
{
int j=i+len;
add(i,j,i-1,j,st[i],ed[i]);
}
}
ll ans1=0,ans2=0;
while(Q--)
{
ll l,r,fl=0;
scanf("%lld%lld",&l,&r);
l=(ans1+l-1)%n+1,r=(ans1+r-1)%n+1;
if(l>r) swap(l,r);
int x=pos[l]-1,y=pos[r]+1;
// printf("%d %d %d %d\n",l,r,x,y);
for(int i=0;i<=m;i++)
{
dp[i]=f[x][y][i];
}
ans1=ans2=0;
for(int i=st[pos[l]];i<l;i++)
{
add1(i);
}
for(int i=r+1;i<=ed[pos[r]];i++)
{
add1(i);
}
ans1=ans2=0;
for(int i=1;i<=m;i++)
{
ans1+=dp[i];
ans2^=dp[i];
}
ans1%=mod;
printf("%lld %lld\n",ans1,ans2);
}
}
return 0;
}
莫队
Robin Hood Archery
https://www.gxyzoj.com/d/hzoj/p/4504
显然,唯一不会输的情况就是所有数的个数都是偶数,所以莫队统计即可
[SNOI2017]一个简单的询问
https://www.gxyzoj.com/d/hzoj/p/P2013
可以将一个询问拆成四部分也就是
然后直接莫队处理就行了
XOR and Favorite Number
https://www.gxyzoj.com/d/hzoj/p/4505
可以先做前缀异或和,每次加点的时候加上当前的异或和异或上
二进制优化
不知道放哪的题(瞎起了个标题)
「SMOI-R1」Apple
https://www.gxyzoj.com/d/hzoj/p/LG10408
卡常题
当不带修的时候,可以用一个高维前缀和,也就是子集dp,但是每次修改都要重新求
可以发现,时间主要消耗在找包含它的集合的时候,因为位数很多,情况很多
可以在更改时先固定前面,处理二进制的后
注意,不要全部枚举,修改时只枚举能够由当前值转移而来的,询问时只枚举能转移到当前值的
时间复杂度
点击查看代码
#include<cstdio>
#define ll long long
using namespace std;
int n,q,a[3000005];
ll f[3000005];
int read()
{
int x=0;
char ch=getchar();
while(ch<48||ch>57) ch=getchar();
while(ch>=48&&ch<=57)
{
x=x*10+ch-48;
ch=getchar();
}
return x;
}
void write(ll x)
{
if(x>9)
{
write(x/10);
}
putchar(x%10+48);
return;
}
int main()
{
n=read(),q=read();
// printf("%d %d\n",n,q);
int tmp1=(n+1)/2,tmp2,tx,ty;
tmp2=n-tmp1;n=(1<<n),tx=(1<<tmp1)-1,ty=((1<<tmp2)-1)<<tmp1;
for(int i=0;i<n;i++)
{
a[i]=read();
f[i]=a[i];
}
for(int j=0;j<tmp1;j++)
{
int t1=1<<j;
for(int i=0;i<n;i++)
{
if(t1&i)
f[i]+=f[i^t1];
}
}
while(q--)
{
int opt,s,val;
opt=read(),s=read();
if(opt==1)
{
int y=s&ty,x=s&tx;
ll ans=0;
for(int i=y;i;i=(i-1)&y) ans+=f[i|x];
ans+=f[s&x];
write(ans);
putchar('\n');
}
else
{
val=read();
int x=(s&tx)^tx;
ll tmp=1ll*val-a[s];
a[s]=val;
for(int i=x;i;i=(i-1)&x) f[i|s]+=tmp;
f[s]+=tmp;
}
}
return 0;
}
String Set Queries
https://www.gxyzoj.com/d/hzoj/p/4506
涉及字符串匹配,考虑 AC 自动机,加入的放入一个,删除的放入另一个,求解时相减即可
但是 AC 自动机是静态的,建好后无法加入删除,考虑二进制分组
可以将新加入的点单独作为一组,建 trie,如果当前组和前面组的字符串数量相同,就将 trie 树合并
合并后,在最末的 trie 上建 fail 指针即可
点击查看代码
#include<cstdio>
#include<string>
#include<iostream>
#include<vector>
#include<queue>
using namespace std;
const int N=300005;
int T,tot,now;
struct ACAM{
int tr[N*2][26],ch[N*2][26],idx,sum[N*2],fail[N*2],val[N*2];
int rt[N],siz[N],cnt;
void ins_tr(int x,string s)
{
for(int i=0;i<s.size();i++)
{
int c=s[i]-'a';
if(!tr[x][c]) tr[x][c]=++idx;
x=tr[x][c];
}
val[x]++;
}
int merge(int x,int y)
{
if(!x||!y) return x+y;
val[x]+=val[y];
for(int i=0;i<26;i++)
{
tr[x][i]=merge(tr[x][i],tr[y][i]);
}
return x;
}
void getfail(int x)
{
queue<int> q;
fail[x]=x;
for(int i=0;i<26;i++)
{
if(tr[x][i])
{
ch[x][i]=tr[x][i];
q.push(ch[x][i]);
fail[ch[x][i]]=x;
}
else ch[x][i]=x;
}
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=0;i<26;i++)
{
if(tr[u][i])
{
ch[u][i]=tr[u][i];
fail[ch[u][i]]=ch[fail[u]][i];
q.push(ch[u][i]);
}
else ch[u][i]=ch[fail[u]][i];
}
sum[u]=val[u]+sum[fail[u]];
}
}
void insert(string s)
{
siz[++cnt]=1,rt[cnt]=++idx;
ins_tr(rt[cnt],s);
while(siz[cnt]==siz[cnt-1])
{
rt[cnt-1]=merge(rt[cnt-1],rt[cnt]);
siz[cnt-1]*=2,cnt--;
}
getfail(rt[cnt]);
}
int query(string s)
{
int ans=0,p;
for(int i=1;i<=cnt;i++)
{
p=rt[i];
for(int j=0;j<s.size();j++)
{
int c=s[j]-'a';
p=ch[p][c];
ans+=sum[p];
}
}
return ans;
}
}add,del;
int main()
{
scanf("%d",&T);
while(T--)
{
int opt;
scanf("%d",&opt);
string s;
cin>>s;
if(opt==1)
{
add.insert(s);
}
if(opt==2)
{
del.insert(s);
}
if(opt==3)
{
printf("%d\n",add.query(s)-del.query(s));
}
fflush(stdout);
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通