Happyunlimited 2019-12-29
原题链接 https://www.luogu.com.cn/problem/P3396



给你一个长度为 n 的序列和 m 个操作,每次操作有两种类型:
1. 询问下标模 x 后为 y 的所有数之和;
2. 修改第 x 个数;
先想想暴力怎么搞:
对于第 1 种操作,我们可以 O(n)的枚举求和,对于第 2 种操作,我们可以 O(1)修改;
long long ans=0;
for(int i=y;i<=n;i+=x) ans+=a[i]; //暴力枚举加和
printf("%lld\n",ans);总的时间复杂度为 O(nm),n2 过 15w?显然不行;
考虑一下第一种操作为什么跑的慢。
如果模数 x 很小时,我们上面的那层 for 循环的复杂度更接近 O(n);反之,如果模数 x 很大时,复杂度反而会小;
这就提供了我们一种思路:
只有当 x 比较大的时候我们才暴力,如果 x 很小,我们直接记录答案!
那么 x 什么时候才算大呢?
一般我们钦定 x > √n 的时候我们就暴力;
这种算法有个响当当的名字:
根号算法是一种很常见的算法;
常见的根号思想有:双向搜索,根号分类讨论,根号重建,复杂度平衡,以及一些根号级别的数据结构如分块和莫队;
这些算法一般是多种暴力算法的结合,一般具有较低的思维难度和编码难度;
——ImmortalCO猫
有的时候,我们可以对一个题想出两个暴力,各有各自的长处和短处。
如果我们能对数据范围进行分块处理,或者两个暴力分别算之后拼接在一起,就用两个合在一起的暴力,实现了正解。
通常这个分界点可以取到 n−−√n
所以叫根号算法。
通过根号算法,我们就能实现两种操作时间复杂度的均分了 。
我们用 dp [ i ][ j ] 记录模 i 为 j 的所有下标所对应的数的和;(这里数组只需开到 √n)
O(n√n)预处理出答案:
for(int i=1;i<=n;i++) //枚举每个数
for(int j=1;j<=sqrt(n);j++) //枚举模几
dp[j][i%j]+=a[i];对于第 1 种操作,如果所给的模数 x < √n,那么我们直接输出答案;否则我们暴力枚举;
时间复杂度 O(√n)
if(C==‘A‘) //求模x为y的和
{
if(x<=sqrt(n)) printf("%lld\n",dp[x][y]); //直接输出
else
{
long long ans=0;
for(int i=y;i<=n;i+=x) ans+=a[i]; //暴力枚举加和
printf("%lld\n",ans);
}
}对于第 2 种操作,我们在将第 x 个数更新的同时,也要把 dp 数组更新一遍;
时间复杂度 O(√n)
for(int i=1;i<=sqrt(n);i++) //更新第x个数所涉及到的所有的池
dp[i][x%i]+=y-a[x];
a[x]=y;总体时间复杂度 O(n√n),这样我们就用几乎暴力的算法过掉了本题qwq
Code:
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int read()
{
char ch=getchar();
int a=0,x=1;
while(ch<‘0‘||ch>‘9‘)
{
if(ch==‘-‘) x=-x;
ch=getchar();
}
while(ch>=‘0‘&&ch<=‘9‘)
{
a=(a<<1)+(a<<3)+(ch-‘0‘);
ch=getchar();
}
return a*x;
}
const int N=150005;
int a[N];
long long dp[400][400]; //dp[i][j]:模i为j的所有下标所对应的数的和,只需开到√n
int n,m,x,y;
char C;
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=n;i++) //枚举每个数
for(int j=1;j<=sqrt(n);j++) //枚举模几
dp[j][i%j]+=a[i];
while(m--)
{
cin>>C;
x=read();y=read();
if(C==‘A‘) //求模x为y的和
{
if(x<=sqrt(n)) printf("%lld\n",dp[x][y]); //直接输出
else //若x>√n,直接暴力!
{
long long ans=0;
for(int i=y;i<=n;i+=x) ans+=a[i]; //暴力枚举加和
printf("%lld\n",ans);
}
}
else //把第x个数改成y
{
for(int i=1;i<=sqrt(n);i++) //更新第x个数所涉及到的所有的池
dp[i][x%i]+=y-a[x];
a[x]=y;
}
}
return 0;
}