思想:对于一个问题的不同数据范围,按不同(复杂度)方式解答。通常按根号为界分别。
理解:根号可以将一个不平均的问题,分块+拼接成一个根号级别平均的问题。
例题
缘幂求余
-
思路:按照快速幂思想^ ,想出了根号级别预处理,O(1)查询的光速幂 ^ *^
-
代码
#include<stdio.h>
#include<math.h>
using namespace std;
const int N=50005;
long long A[N],B[N];
int main() {
int x,n,y;
long long mod=998244352;
scanf("%d%d",&x,&n);
long long l=35000;
A[0]=B[0]=1;
for(int i=1;i<=l;i++) A[i]=A[i-1]*x%mod;
for(int i=1;i<=l;i++) B[i]=B[i-1]*A[l]%mod;
for(int i=1;i<=n;i++) {
scanf("%d",&y);
printf("%d ",A[y%l]*B[y/l]%mod);
}
return 0;
}
Mod 统计
-
思路
当模数时,可以用方案1:直接从r每次递增p统计和,这样会发现p越大越快,最慢也是
否则会发现,我们利用p较小的特点,预处理出sum[p][r]表示%p为r的值的和 -
代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
const int M=1e3+5;
int sum[M][M],a[N];
int main() {
int n,m,k;
scanf("%d%d",&n,&m);
k=(int)(sqrt(n));
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=k;i++) {
for(int j=1;j<=n;j++) {
sum[i][j%i]+=a[j];
}
}
for(int i=1;i<=m;i++) {
char opt[5];
int x,y;
scanf("%s%d%d",&opt,&x,&y);
if(opt[0]=='A') {
if(x<=k) printf("%d\n",sum[x][y]);
else {
int res=0;
for(int j=y;j<=n;j+=x) {
res+=a[j];
}
printf("%d\n",res);
}
}
else {
int tmp=y-a[x];
for(int j=1;j<=k;j++) sum[j][x%j]+=tmp;
a[x]=y;
}
}
return 0;
}
子集求和
-
思路:
这道题稍难一点qwq,很明显对于一个集合的个数分类: -
元素个数,我们可以暴力枚举每个子集
-
否则,首先m个集合的元素个数总和不超过,因此元素个数的大集合数不超过个.
我们预处理出sum[s]表示s子集值的和,复杂度
修改s集合时,直接将其Lazy[s]+=val;
但是注意!!有些小集合和大集合会有交集,因此我们预处理出cnt[i][j]表示小集合i与大集合j的交集元素个数,在小集合更新时,枚举所有大集合,该 sum[j]+=val * cnt[i][j] ;在输出小集合和时,同理枚举所有大集合,加上Lazy[j] * cnt[i][j] -
代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int M=400;
typedef long long ll;
ll a[N],sum[N],Add[N];
vector<int> V[N],tot1[N],tot2[N],B;
int cnt[5005][5005],cnt2[M][M],num[N],cb,cm;
bool mark[N];
int main() {
int n,m,q,sq;
scanf("%d%d%d",&n,&m,&q);
sq=(int)(sqrt(n));
for(int i=1;i<=n;i++) {
scanf("%lld",&a[i]);
}
for(int i=1;i<=m;i++) {
scanf("%d",&num[i]);
if(num[i]>sq) B.push_back(i);
ll res=0;
for(int j=1;j<=num[i];j++) {
int x; scanf("%d",&x);
V[i].push_back(x); res+=a[x];
}
if(num[i]>sq) sum[i]=res;
}
int j;
for(int i=1;i<=m;i++) {
if(num[i]<=sq) {
for(j=0;j<V[i].size();j++) tot1[V[i][j]].push_back(1ll*i);
}
else {
for(j=0;j<V[i].size();j++) tot2[V[i][j]].push_back(1ll*i);
}
}
for(int k=1;k<=n;k++) {
for(int i=0;i<tot2[k].size();i++) {
for(j=0;j<tot2[k].size();j++) if(i!=j)cnt2[tot2[k][i]][tot2[k][j]]++;
for(j=0;j<tot1[k].size();j++) {
cnt[tot2[k][i]][tot1[k][j]]++;
}
}
}
for(int t=1;t<=q;t++) {
char ch[5];
int x,y;
scanf("%s",ch);
if(ch[0]=='?') {
scanf("%d",&x);
if(num[x]<=sq) {
ll res=0;
for(j=0;j<V[x].size();j++)res+=a[V[x][j]];
for(j=0;j<B.size();j++) {
res+=Add[B[j]]*cnt[B[j]][x];
}
printf("%lld\n",res);
}
else {
ll res=0;
for(j=0;j<B.size();j++) {
// printf("%d %d %d\n",B[j],cnt2[x][B[j]],Add[B[j]]);
res+=Add[B[j]]*cnt2[x][B[j]];
}
printf("%lld\n",sum[x]+Add[x]*num[x]+res);
}
}
else {
scanf("%d%d",&x,&y);
if(num[x]<=sq) {
for(j=0;j<V[x].size();j++)a[V[x][j]]+=y;
for(j=0;j<B.size();j++) sum[B[j]]+=y*cnt[B[j]][x];
}
else {
Add[x]+=y;
}
}
}
return 0;
}
雅加达的摩天楼
-
思路:
啊这读完题都会感受到最短路那味,可是恶心人的是,怎么也想不到要用根号分治优化建图.还有这道题跑最短路是以摩天楼为点(不是doge) -
时,直接暴力将向其它能到的点直接连边
-
否则,建一个图框架:令 ,将每个点拆分成len个虚拟节点+1个它本身,显然len个虚拟节点都要指向它本身。有len个虚拟层,如第k层的节点分别为(0~n-1的第k个的虚拟节点构成)
第k个虚拟层,将里面的点每隔k距离连边。
最后,我们将向的第个虚拟节点连边
ps.代码中节点编号还是按偏移量n来表现
总结一下,这利用了广度撒网的思想来顶住复杂度上限(注意常数) -
代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
const int M=2e7;
const int NM=1e7+5;
int n,m;
int len,B[N],p[N];
int nxt[M],to[M],head[NM],ln[M],dis[NM],num;
bool mark[NM];
struct node {
int p,w;
bool operator<(const node &u) const{return w>u.w;}
};
void add_edge(int u,int v,int w) {
nxt[++num]=head[u],to[num]=v,ln[num]=w,head[u]=num;
}
void Build1(int x) {
for(int i=B[x]+p[x],j=1;i<n;i+=p[x],j++) {
add_edge(B[x],i,j);
}
for(int i=B[x]-p[x],j=1;i>=0;i-=p[x],j++) {
add_edge(B[x],i,j);
}
}
void init() {
//建立len个维度
for(int s=1;s<=len;s++) {
for(int i=0;i<n-s;i++) {
add_edge(i+s*n,i+s+s*n,1),add_edge(i+s+s*n,i+s*n,1);
}
}
for(int i=0;i<n;i++) {
for(int s=1;s<=len;s++) {
add_edge(i+s*n,i,0);
}
}
}
priority_queue<node> Q;
void DJ(int s) {
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
Q.push((node){s,0});
while(!Q.empty()) {
int u=Q.top().p; Q.pop();
if(mark[u]) continue;
mark[u]=true;
for(int i=head[u];i;i=nxt[i]) {
int v=to[i];
if(!mark[v]&&dis[v]>dis[u]+ln[i]) {
dis[v]=dis[u]+ln[i];
Q.push((node){v,dis[v]});
}
}
}
}
int main() {
scanf("%d%d",&n,&m);
len=sqrt(n);
len=min(len,100);
init();
for(int i=0;i<m;i++) scanf("%d%d",&B[i],&p[i]);
for(int i=0;i<m;i++) {
if(p[i]>len) Build1(i);
else add_edge(B[i],B[i]+p[i]*n,0);
}
DJ(B[0]);
if(dis[B[1]]==0x3f3f3f3f) printf("-1");
else printf("%d\n",dis[B[1]]);
return 0;
}
跑步
- 思路:
类上 - 代码:
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
typedef long long ll;
ll f[N][2],g[N][2],sum[N];
int main() {
int n,k;ll p;
scanf("%d%lld",&n,&p);
k=(int)(sqrt(n));
f[0][0]=f[0][1]=1;
g[0][0]=1;
for(int j=1;j<k;j++) {
for(int i=1;i<=n;i++) {
f[i][j&1]=f[i][j&1^1];
if(i>=j) f[i][j&1]+=f[i-j][j&1],f[i][j&1]%=p;
}
}
sum[0]=1;
for(int j=1;j<=n/k;j++) {
for(int i=0;i<=n;i++) g[i][j&1]=0;
for(int i=k;i<=n;i++) {
g[i][j&1]=g[i-k][j&1^1];
if(i>=j) g[i][j&1]+=g[i-j][j&1],g[i][j&1]%=p;
sum[i]+=g[i][j&1],sum[i]%=p;
}
}
ll ans=0;
for(int i=0;i<=n;i++) {
ans+=f[i][(k-1)&1]*sum[n-i]%p,ans%=p;
}
printf("%lld",ans);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人