简单数学题乱写
数学
数论分块:
结论:整除分块是用于快速处理形似 \(\sum _ {i=1} ^n \lfloor \dfrac n i\rfloor\) 的式子的办法,对于这玩意通过严谨的数学推理(打表) 我们发现实际上这些取值是连续且有规律的,即
如果一个值的开始位置时为 \(l\) 那么它的结束位置 \(r\) 就是 \(\lfloor \dfrac n {\lfloor \frac n l \rfloor} \rfloor\)
引理1
证明略
引理2
对于一个较大的 \(n\) 我们显然会发现,我们后面的这个下取整取值并不是每一次都随着 \(i\) 而变化的,它是呈块状分布的,同时这个下取整的值我们也能得到,应该是共有 \(2\sqrt n\) 个值
结论:
通过严谨的数学推理(打表) 我们发现实际上这些取值是有规律的,即
如果一个块的开始位置时 \(l\) 那么它的结束位置 \(r\) 就是 \(\lfloor \dfrac n {\lfloor \frac n l \rfloor} \rfloor\)
P2261 [CQOI2007]余数求和
给定正整数 \(n\) 和 \(k\),请计算
\[G(n,k) = \sum_{i = 1} ^n k \bmod i \]
对于这个题来说,我们首先考虑把 \(k \bmod i\) 拆开 \(k-\lfloor \dfrac k i \rfloor*i\)
那么 \(k\) 肯定不用管,原式先把 \(k\) 提出来 \(kn-\sum _{i=1}^n \lfloor \dfrac k i \rfloor*i\)
后面的东西就可以用我们说的数论分块了
时间复杂度据说是 \(O(\sqrt k)\)
ll ans = n * k;
for (int l = 1, r = 0; l <= n; l = r + 1) {
if (k / l) r = min(k / (k / l), n);
else r = n;
ans -= (k / l) * (r - l + 1) * (l + r) / 2;
}
P2260[清华集训2012]模积和
\[计算:~\sum _{i = 1} ^ n\sum_{j=1}^m(n\bmod i)\times (m\bmod j),i \not = j \]
\(i\not = j\) 看着就不爽,我们先容斥一下,把它拆开
两边分开讨论处理,首先左边看起来 \(n\bmod i\) 和 \(j\) 没啥关系,我们先提出来
然后我们把 \(n,m\) 提出来
左右两边可以分别进行整除分块
然后我们考虑右边,同同样的方式
对于 \(\dfrac n i\) 和 \(\dfrac m i\) 我们可以一起处理,大不了多分几块
就是对于后面的 \(i^2\) 得想个办法前缀和处理一下
于是我们就有
所以前面的东西我们也就可以预处理了
前面的前缀和公式证明如下:
设 \(f(x)\) 表示 \(x^3-(x-1)^3\)
我们计算
于是
又由定义可知:\(f(x)=x^3-(x-1)^3\)
所以
所以我们把两个式子联立一下就有
最后让我尽可能的独立写出一份代码!
read(n, m);
if (n > m) swap(n, m);
tmp = n * n % mod;
for (int l = 1, r; l <= n; l = r + 1) {
if (n / l) r = min(n / (n / l), n);
else r = n;
tmp = (tmp - (n / l) * ((l + r) * (r - l + 1) / 2 % mod) % mod + mod) % mod;
}
ans = tmp;
tmp = m * m % mod;
for (int l = 1, r; l <= m; l = r + 1) {
if (m / l) r = min(m / (m / l), m);
else r = m;
tmp = (tmp - (m / l) * ((l + r) * (r - l + 1) / 2 % mod) % mod + mod) % mod;
}
ans = ans * tmp % mod;
tmp = 0;
for (int l = 1, r; l <= n; l = r + 1) {
if (n / l) r = min(n / (n / l), n);
else r = n;
tmp = (tmp + (n / l) * ((l + r) * (r - l + 1) / 2 % mod) % mod) % mod;
}
tmp = tmp * m % mod;
ans = (ans + tmp) % mod;
tmp = 0;
for (int l = 1, r; l <= n; l = r + 1) {
if (m / l) r = min(m / (m / l), n);
else r = n;
tmp = (tmp + (m / l) * ((l + r) * (r - l + 1) / 2 % mod) % mod + mod) % mod;
}
tmp = tmp * n % mod;
ans = (ans + tmp) % mod;
ans = ((ans - n * n % mod * m % mod) % mod + mod) % mod;
tmp = 0;
for (int l = 1, r; l <= n; l = r + 1) {
if (n / l) r = min(n / (n / l), m / (m / l));
else r = n;
tmp = ((tmp + (n / l) * (m / l) % mod * (sum(r) - sum(l - 1)) % mod) % mod + mod) % mod;
}
ans = (ans - tmp) % mod + mod;
write(ans % mod, '\n');
return 0;
}
//write:RevolutionBP
顺便压缩了一下,应该这个长度的比较合适
inline int sum(int x) {
return x * (x + 1) % mod * (2 * x + 1) % mod * 3323403 % mod;
}
int left1, left2;
int right1, right2, right3;
signed main(){
#ifndef ONLINE_JUDGE
freopen("1.in", "r", stdin);
freopen("1.out", "w", stdout);
#endif
read(n, m);
if (n > m) swap(n, m);
left1 = n * n % mod;
left2 = m * m % mod;
for (int l = 1, r; l <= n; l = r + 1) {
if (n / l) r = min(n, n / (n / l));
else r = n;
left1 = (left1 - (n / l) * ((l + r) * (r - l + 1) / 2 % mod) % mod + mod) % mod;
right1 = (right1 + (n / l) * ((l + r) * (r - l + 1) / 2 % mod) % mod) % mod;
}
for (int l = 1, r; l <= m; l = r + 1) {
if (m / l) r = min(m, m / (m / l));
else r = m;
left2 = (left2 - (m / l) * ((l + r) * (r - l + 1) / 2 % mod) % mod + mod) % mod;
}
for (int l = 1, r; l <= n; l = r + 1) {
if (n / l) r = min(n / (n / l), m / (m / l));
else r = n;
right2 = (right2 + (m / l) * ((l + r) * (r - l + 1) / 2 % mod) % mod) % mod;
right3 = ((right3 + (n / l) * (m / l) % mod * ((sum(r) - sum(l - 1)) % mod + mod) % mod) % mod + mod) % mod;
}
right1 = right1 * m % mod;
right2 = right2 * n % mod;
tmp = left1 * left2 % mod;
ans = (tmp - (n * n % mod * m % mod - (right1 + right2 - right3) % mod) % mod + mod) % mod;
write((ans + mod) % mod, '\n');
return 0;
}
P3935 Calculating
若 \(x\) 分解质因数结果为 \(p_1^{k_1} p_2^{k_2}\dots p_n^{k_n}\),令 \(f(x)=(k_1+1)(k_2+2)\dots(k_n+1)\),求
\[\sum_{i=l}^rf(i) \]
首先我们肯定得容斥一下
然后我们去观察 \(f(x)\) 的定义发现其实就是因数个数,所以我们考虑改变思考方式,对于 \(1\sim n\) 的因数个数之和,我们可以转化成统计一个因数在哪些数中出现过
所以原题就很简单了,我们枚举每一个数,计算一下 \(\lfloor \dfrac n i\rfloor\),那么最终答案就应该是
直接整除分块即可
线性筛
P2158 [SDOI2008] 仪仗队
作为体育委员,C 君负责这次运动会仪仗队的训练。仪仗队是由学生组成的 \(N \times N\) 的方阵,为了保证队伍在行进中整齐划一,C 君会跟在仪仗队的左后方,根据其视线所及的学生人数来判断队伍是否整齐(如下图)。
现在,C 君希望你告诉他队伍整齐时能看到的学生人数。
首先第一步我们转化题意,如果存在一个队员我们看不见,那么它的 \(\gcd(x,y)\) 必定 \(>1\)
原因很简单,因为如果我们出现了一个点它的 \(g=\gcd \not = 1\) 则有一个必然的现象就是 \(x/g,y/g\) 这个点会挡住它
所以题意就转化成了给定你一个 \(n * n\) 区间,问你其中有多少个坐标互质
我们考虑若 \((i,j)\) 这个点满足条件,则 \((j,i)\) 必然也满足条件,所以我们直接统计 \(j<i\) 的情况,最后再乘上 \(2\) 即可
我们固定住 \(i\) 考虑这一行有多少个与 \(i\) 互质,由于 \(j<i\) 所以这就是欧拉函数 \(\varphi\) 的定义
复习一下欧拉筛:
我们求 \(\varphi(k)\)时,当 \(k\) 是质数时,显然 \(\varphi(k) = k-1\)
当 \(k\) 不是质数时:
分开考虑
我们已知
我们枚举到 \(i\) 时
若 \(i \mid k\),且 \(i\) 为质数,则我们有 \(\varphi(i*k)=\varphi(k)*i\) 原因是 \(i\) 已经是一个质因子了,它对于里面的那堆玩意乘起来没有作用,只是让 \(N\) 翻了个倍,
若 \(i\nmid k\),且 \(i\) 为质数,则我们有 \(\varphi(i\times k)= \varphi(k) \times (i-1)\)
因此我们发现,上面除以下面以后
然后我们把线性筛写出来
inline void get_prime() {
rep (i, 2, N - 1) {
if (!st[i]) {
prime[++cnt] = i;
continue;
}
rep (j, 1, cnt) {
if (prime[j] * i > N - 1) break;
st[prime[j] * i] = true;
if (i % prime[j] == 0) break;
}
}
}
我们观察这一段代码,看看如何把上面的三条性质代入求得 \(\varphi\) 数组
首先比较容易的是当 \(k\) 为质数时,我们直接在 if(!st[i]) prime[++cnt] = i
的地方 后面加一句 phi[i] = i - 1
然后第二条性质的话要求整除,我们可以利用 i % prime[j] == 0
时,\(prime_j\) 是 \(i\) 的最小质因子的性质,后面加一句 phi[i * prime[j]] = phi[i] * prime[j]
第三条的性质的话,我们就可以在最后放上一句 phi[i * prime[j]] = (i - 1) * prime[j]
那么完整代码如下:
inline void get_phi() {
phi[0] = phi[1] = 1; //注意这里的 phi[1] 定义为 1
rep (i, 2, n) {
if (!st[i]) prime[++cnt] = i, phi[i] = i - 1;
for (int j = 1; j <= cnt and prime[j] * i <= n; j++) {
st[i * prime[j]] = true;
if (i % prime[j] == 0) {
phi[i * prime[j]] = phi[i] * prime[j];
break;
}
phi[i * prime[j]] = phi[i] * (prime[j] - 1);
}
}
}
所以这题我们也就可以轻松的解决了
read(n);
n --;
if (!n) return write(0, '\n'), 0;
get_phi();
rep (i, 1, n)
ans += phi[i];
write(ans << 1 | 1, '\n');
P4626 一道水题 II
求一个能被 \([1,n]\) 内所有数整除的最小数字,并对 \(1e8+7\) 取模
数据范围:\(1\le n \le 10^8\)
首先第一步我们转化题意,对于一个区间内都整除的数,应该就是他们的最小公倍数
我们接下来考虑怎么做这个玩意
首先我们肯定知道,一堆数的最小公倍数应该是等于
那么我们就要找到每一个质因子的最高次项
首先对于一个小于 \(\sqrt n\) 的数,我们的 \(k_i\) 必然是 \(\lfloor \log_{x_i}n \rfloor\)
对于一个大于 \(\sqrt n\) 的数,我们的 \(k_i\) 就是 1
所以我们直接开始筛质数,然后小于 \(\sqrt n\) 的我们算一下贡献,大于的我们再算一下就行
但是这里的数据卡的很紧,我们每次算 \(\log\) 的话可能会 TLE
我们用一下换底公式
令 \(k=2\) 则我们上面的 \(\log _{x_i} n\) 就等于 \(\dfrac {\log_2 n} {\log_2 {x_i}}\)
inline void get_prime(int n) {
limit = sqrt(n);
rep (i, 2, n) {
if (!st[i]) {
prime[++cnt] = i;
if (i <= limit) ans = ans * 1ll * ksm(i, log2(n) / log2(i)) % mod;
else ans = ans * 1ll * i % mod;
}
for (int j = 1; j <= cnt and prime[j] * i <= n; j++) {
st[i * prime[j]] = 1;
if (i % prime[j] == 0) break;
}
}
}
int main(){
read(n);
get_prime(n);
write(ans, '\n');
return 0;
}
//write:RevolutionBP
CF1017F The Neutral Zone
定义 \(exlog_f(p_1^{\alpha_1}p_2^{\alpha_2}\dots p_n^{\alpha_n})= >\alpha_1f(p_1)+\alpha_2 f(p_2)\dots \alpha_nf(p_n)\)
\(f(x) = Ax^3+Bx^2+Cx+D\)
求 \(\sum_{i=1}^n exlog_f(i)\)
空间限制: 16MB 时间限制:5s
结果对 \(2^{32}\) 取模
取模的问题很好解决,开 \(unsigned~int\) 即可
对于 \(exlog_f(p_1^{\alpha_1}p_2^{\alpha_2}\dots p_n^{\alpha_n})\) 我们可以发现,\(exlog_f(a \times b) = exlog_f(a)+exlog_f(b)\)
原因不用多说,因此我们考虑原问题可以被搞成
这玩意我们考虑对于 \(n!\) 来说我们很容易得到它的质因子的指数即 \(p_i\) 的指数为
这个证明就不必要了
那么答案我们就可以表示为
这样的写法时间复杂度大概在 \(O(n)\)
但是我们这样写的空间复杂度大概为 \(286MB\)
我们考虑,其实我们如果要是用埃筛呢?
这就给了我们很大的启示,我们可以把原来的 \(n\) 拆成很多段,这样的话,每一段内部我们可以用区间筛法搞掉
同时这样的话,我们不需要筛到 \(n\),只需要筛到 \(\sqrt n\) 即可
这样算下来的话我们的空间复杂度很小,只有 \(O(\sqrt n + B)\),B是块长
但是同样时间复杂度就有点高可能会达到 \(O(n\log \log \sqrt n)\)
但是还是肯定能过的
这道确实是一道好题啊!!!
int n, blo, ans, a, b, c, d, cnt, prime[2100];
bool st[17322];
inline int F(int x) {
return a * x * x * x + b * x * x + c * x + d;
}
inline int calc(int x) {
int k = 0, y = n / x;
while (y) {
k += y;
y /= x;
}
ans += k * F(x);
return ans;
}
inline void get_prime() {
rep(i, 2, blo) {
if (!st[i])
prime[++cnt] = i, calc(i);
for (int j = 1; j <= cnt and prime[j] * i <= blo; j++) {
st[prime[j] * i] = true;
if (i % prime[j] == 0)
break;
}
}
}
signed main() {
#ifndef ONLINE_JUDGE
freopen("1.in", "r", stdin);
freopen("1.out", "w", stdout);
#endif
read(n, a, b, c, d);
blo = sqrt(n);
get_prime();
int l = 1, r = blo;
while (r < n) {
l = r + 1, r = min(r + blo, n);
rep(i, l, r)
st[i - l + 1] = true;
rep(i, 1, cnt) {
for (int j = (l + prime[i] - 1) / prime[i] * prime[i]; j <= r; j += prime[i])
st[j - l + 1] = false;
}
rep(i, l, r) if (st[i - l + 1]) calc(i);
}
write(ans, '\n');
return 0;
}
// write:RevolutionBP
P6810 「MCOI-02」Convex Hull 凸包
\[\sum_{i=1}^n\sum_{j=1}^md(i)d(j)d(\gcd(i,j)) \]其中 \(d(x)\) 表示 \(x\) 的约数个数
\(1 \le n,m \le 2\times 10^6~~~~~\)\(1\le p \le 10^9\)
这个题基本上就是自己独立完成的
直接推两个式子即可
inline void get_d(int n) {
rep (i, 1, n)
for (int j = i; j <= n; j += i)
d[j]++;
}
inline int calc(int n, int k) {
int res = 0;
for (int i = k; i <= n; i += k) res = (res + d[i]) % p;
return res;
}
int main(){
read(n, m, p);
if (n > m) swap(n, m);
get_d(m);
rep (i, 1, n) ans = (ans + 1ll * calc(n, i) * calc(m, i) % p) % p;
write(ans, '\n');
return 0;
}
//write:RevolutionBP
莫比乌斯反演
基本形式: