自然数幂和题解报告
原文链接:https://www.cnblogs.com/zsxuan/p/18016759
1. 线性逆元板子:https://www.luogu.com.cn/problem/P3811
题意:
线性求出
题解:
线性逆元
且
时间复杂度
view
#include <bits/stdc++.h> typedef long long ll; const int N = 3E6 + 5; int MOD; int add(int a, int b) { return ( 1LL * (1LL * a + b) % MOD + MOD) % MOD; } int mul(int a, int b) { return (1LL * a * b % MOD + MOD) % MOD; } int inv[N], fac[N], ifac[N]; struct invInit { invInit() { fac[0] = ifac[0] = inv[1] = 1; for (int i = 1; i < N; i++) { if (i > 1) inv[i] = 1LL * (MOD - MOD / i) * inv[MOD % i] % MOD; fac[i] = 1LL * fac[i - 1] * i % MOD; ifac[i] = 1LL * ifac[i - 1] * inv[i] % MOD; } } }; signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif int n, p; std::cin >> n >> p; MOD = p; invInit __init__inv; for (int i = 1; i <= n; i++) { std::cout << inv[i] << "\n"; } return 0; }
2. 自然数幂和 1 :http://oj.daimayuan.top/course/22/problem/1106
题意:
求
题解 1 :
算两次的差分思想:
时间复杂度显然的
view
#include <bits/stdc++.h> typedef long long ll; const int N = 2E3 + 10; int k, n, p; int s[N], pw[N]; int inv[N], fac[N], ifac[N]; struct invInit { invInit() { inv[1] = fac[0] = ifac[0] = 1; for (int i = 1; i < N; i++) { if (i > 1) inv[i] = 1LL * (p - p / i) * inv[p % i] % p; fac[i] = 1LL * fac[i - 1] * i % p; ifac[i] = 1LL * ifac[i - 1] * inv[i] % p; } } }; int comb(int n, int m) { if (n < 0 || m < 0 || n < m) return 0; return 1LL * fac[n] * ifac[n - m] % p * ifac[m] % p; } signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif std::cin >> k >> n >> p; invInit __init__inv; pw[0] = 1; for (int i = 1; i <= k + 1; i++) pw[i] = 1LL * pw[i - 1] * (n + 1) % p; s[0] = n; for (int i = 1; i <= k; i++) { int P = (pw[i + 1] - 1 + p) % p; for (int j = 0; j < i; j++) P = (1LL * P - 1LL * comb(i + 1, j) * s[j] % p + p) % p; int Q = inv[i + 1]; s[i] = 1LL * P * Q % p; } std::cout << s[k] << "\n"; return 0; }
题解 2 :
于是至少可以
不妨由递推式
复杂度瓶颈在
view
#include <bits/stdc++.h> typedef long long ll; const int N = 2010; int MOD; int add(int a, int b) { return ( 1LL * (1LL * a + b) % MOD + MOD) % MOD; } int mul(int a, int b) { return (1LL * a * b % MOD + MOD) % MOD; } int qpow(int a, ll n) { int res=1; for(;n;a=mul(a,a),n>>=1) if(n&1) res=mul(res,a); return res; } int k, n, p; int x[N], y[N]; int lagrange(int k, int * x, int *y, int n) { int fk = 0; for (int i = 0; i <= n; i ++) { int P = y[i], Q = 1; for (int j = 0; j <= n; j++) if (j != i) { P = mul(P, add(k, -x[j])); Q = mul(Q, add(x[i], -x[j])); } Q = qpow(Q, MOD - 2); fk = add(fk, mul(P, Q)); } return fk; } signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif std::cin >> k >> n >> p; MOD = p; for (int i = 0; i <= k + 1; i++) x[i] = i; // S_{n + 1}(k) = S_{n}(k) + (n + 1)^k, S_{0}(k) = 0 y[0] = 0; for (int i = 1; i <= k + 1; i++) y[i] = add(y[i - 1], qpow(i, k)); int ans = lagrange(n, x, y, k + 1); std::cout << ans << "\n"; return 0; }
3. 自然数幂和 2 :https://www.luogu.com.cn/problem/CF622F
续借上文的自然数幂和 1 ,考虑如何优化。
首先是
观察到
于是借助线性筛有
显然筛法的瓶颈在于质数密度。根据质数定理
求前缀和即得到
view
#include <bits/stdc++.h> typedef long long ll; const int N = 1E6 + 10; const int MOD = 1E9 + 7; int n, k; int y[N], inv[N], fac[N], ifac[N]; int p[N], pr[N], tot; int add(int a, int b) { return ( 1LL * (1LL * a + b) % MOD + MOD) % MOD; } int mul(int a, int b) { return (1LL * a * b % MOD + MOD) % MOD; } int qpow(int a, ll n) { int res=1; for(;n;a=mul(a,a),n>>=1) if(n&1) res=mul(res,a); return res; } struct eulaSieveInit { eulaSieveInit() { y[1] = 1; for (int i = 2; i < N; i++) { if (!p[i]) p[i] = i, pr[++tot] = i, y[i] = qpow(i, k); for (int j = 1; j <= tot && i * pr[j] < N; j++) { p[i * pr[j]] = pr[j]; y[i * pr[j]] = mul(y[i], y[pr[j]]); if (p[i] == pr[j]) { break; } } } } }; struct invInit { invInit () { inv[1] = fac[0] = ifac[0] = 1; for (int i = 1; i < N; i++) { if (i > 1) inv[i] = mul(MOD - MOD / i, inv[MOD % i]); fac[i] = mul(fac[i - 1], i); ifac[i] = mul(ifac[i - 1], inv[i]); } } }; int consecutive_lagrange(int k, int y[], int n) { int fk = 0; std::vector<int> pre(n + 2), suf(n + 2); pre[0] = k; suf[n + 1] = 1; for (int i = 1; i <= n; i++) pre[i] = mul( pre[i - 1] , add( k , -i ) ); for (int i = n; i >= 0; --i) suf[i] = mul( suf[i + 1] , add( k , -i ) ); for (int i = 0; i <= n; i++) { int sgn = ((n - i) & 1) ? -1 : 1; int res = mul( y[i] , sgn ); int P = mul( i > 0 ? pre[i - 1] : 1, suf[i + 1] ); int Q = mul(ifac[i] , ifac[n - i]); res = mul( res , mul( P , Q ) ); fk = add( fk , res ); } return fk; } signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif std::cin >> n >> k; invInit __init__inv; y[0] = 0; eulaSieveInit __init_eulaSieve; for (int i = 1; i <= k + 1; i++) y[i] = add(y[i], y[i - 1]); int ans = consecutive_lagrange(n, y, k + 1); std::cout << ans << "\n"; return 0; }
4. 拉格朗日插值板子 1 :https://www.luogu.com.cn/problem/P4781
题意:
对于一个关于
给出
题解:
已知
此时逆元无法线性预处理,需要使用快速幂。
将
时间复杂度
view
#include <bits/stdc++.h> typedef long long ll; const int N = 2010; const int MOD = 998244353; int add(int a, int b) { return ( 1LL * (1LL * a + b) % MOD + MOD) % MOD; } int mul(int a, int b) { return (1LL * a * b % MOD + MOD) % MOD; } int qpow(int a, ll n) { int res=1; for(;n;a=mul(a,a),n>>=1) if(n&1) res=mul(res,a); return res; } int k, n; int x[N], y[N]; int lagrange(int k, int * x, int *y, int n) { int fk = 0; for (int i = 0; i <= n; i ++) { int P = y[i], Q = 1; for (int j = 0; j <= n; j++) if (j != i) { P = mul(P, add(k, -x[j])); Q = mul(Q, add(x[i], -x[j])); } Q = qpow(Q, MOD - 2); fk = add(fk, mul(P, Q)); } return fk; } signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif std::cin >> n >> k; for (int i = 0; i < n; i++) { std::cin >> x[i] >> y[i]; } std::cout << lagrange(k, x, y, n - 1) << "\n"; return 0; }
5. 拉格朗日插值板子 2 :https://www.luogu.com.cn/problem/P5667
题意:
给定一个
题解:
首先不难有连续插值公式
可以通过
但求出
6. 拉格朗日插值水题 1 :https://www.luogu.com.cn/problem/P4593
题意:
有一些怪物,每只怪物血量各不相同。血量最高的怪物为
现在手中有无限张“亵渎”卡,一张卡的效果为
1.对所有怪物造成一点伤害
2.每有一只怪物死亡,重复一次法术
每次使用“亵渎”卡后,每只收到伤害怪物会贡献的分数为
题解:
首先确定
让
已知怪物血量不重复。观察空缺血量
于是直到最后一个空缺血量
任意一只怪物对每次“亵渎”卡的贡献的
不难发现死亡消失的怪物不会产生贡献。
单独考虑第
显然处理
于是击杀所有怪物可以获得的贡献为
后半部分
不难发现
瓶颈在于
注意线性筛的写法,每次需要初始化质数数组,不然只能筛一次函数。(被演了)
总复杂度
view
#include <bits/stdc++.h> typedef long long ll; const int N = 60; const int MOD = 1E9 + 7; ll n, m; int a[N], y[N]; int p[N], pr[N], tot; int inv[N], fac[N], ifac[N]; int add(int a, int b) { return ( 1LL * (1LL * a + b) % MOD + MOD) % MOD; } int mul(int a, int b) { return (1LL * a * b % MOD + MOD) % MOD; } int qpow(int a, ll n) { int res=1; for(;n;a=mul(a,a),n>>=1) if(n&1) res=mul(res,a); return res; } struct eulaSieveInit { eulaSieveInit() { tot = 0; for (int i = 1; i < N; i++) p[i] = 0; y[1] = 1; for (int i = 2; i < N; i++) { if (!p[i]) p[i] = i, pr[++tot] = i, y[i] = qpow(i, m + 1); for (int j = 1; j <= tot && i * pr[j] < N; j++) { p[i * pr[j]] = pr[j]; y[i * pr[j]] = mul(y[i], y[pr[j]]); if (p[i] == pr[j]) { break; } } } } }; struct invInit { invInit() { inv[1] = fac[0] = ifac[0] = 1; for (int i = 1; i < N; i++) { if (i > 1) inv[i] = mul(MOD - MOD / i, inv[MOD % i]); fac[i] = mul(fac[i - 1], i); ifac[i] = mul(ifac[i - 1], inv[i]); } } }; int consecutive_lagrange(int k, int *y, int n) { int fk = 0; std::vector<int> pre(n + 2), suf(n + 2); for (int i = 0; i <= n; i++) pre[i] = i > 0 ? mul(pre[i - 1], k - i) : k; suf[n + 1] = 1; for (int i = n; i >= 0; --i) suf[i] = mul(suf[i + 1], k - i); for (int i = 0; i <= n; i++) { int sgn = ((n - i) & 1) ? -1 : 1; int res = mul( i > 0 ? pre[i - 1] : 1, suf[i + 1]); int P = mul(y[i], sgn); int Q = mul(ifac[i], ifac[n - i]); fk = add(fk, mul(res, mul(P, Q))); } return fk; } signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif int tc = 0; std::cin >> tc; while (tc--) { std::cin >> n >> m; invInit __init__inv; eulaSieveInit __init__eulaSieve; for (int i = 1; i < N; i++) y[i] = add(y[i], y[i - 1]); for (int i = 1; i <= m; i++) { std::cin >> a[i]; } std::sort(a + 1, a + m + 1); int ans = 0; for (int i = 1; i <= m + 1; i++) { ans = add(ans, consecutive_lagrange(add(n , - a[i - 1]), y, m + 2)); } for (int i = 1; i <= m + 1; i++) { for (int j = i; j <= m; j++) { ans = add(ans, -qpow(add(a[j], - a[i - 1]), m + 1)); } } std::cout << ans << "\n"; } return 0; }
7. 拉格朗日插值水题 2 :https://vjudge.net/problem/BZOJ-3453
题意:
给定
求
题解:
经过验算
并且注意到
不妨试着直接写出式子
观察到可以写成一个式子
前置定理
幂和阶定理:
证明:
由积累(大雾),通过
递推式计算过程略。
若
推论:
证明:
已知多项式的阶数只有最高项的幂次确定,于是多项式乘以常数阶数不变。
由幂和阶定理,原式结果为关于
于是
多项式等差前缀和升阶定理:关于
证明:
由幂和阶定理的推论,原式为关于
于是
前缀和从
由这几个典型定理,还能观察到
我们可以考虑求出
似乎有点难,但我们可以先算出差分数组。
耿直地,可以以低于
通过
于是可通过
瓶颈是
考虑简化,冷静分析。
考虑
于是可以直接
继而有
再次进行
连续拉格朗日插值
其中
特别注意这里的
要特别注意
view
#include <bits/stdc++.h> typedef long long ll; const int N = 2E2 + 10; const int MOD = 1234567891; int k, a, n, d; int inv[N], fac[N], ifac[N]; int f[N], g[N], h[N]; int add(int a, int b) { return ( 1LL * (1LL * a + b) % MOD + MOD) % MOD; } int mul(int a, int b) { return (1LL * a * b % MOD + MOD) % MOD; } int qpow(int a, ll n) { int res = 1; for (; n; a=mul(a, a), n>>=1) if(n&1) res=mul(res, a); return res; } struct invInit { invInit() { fac[0] = ifac[0] = inv[1] = 1; for (int i = 1; i < N; i++) { if (i > 1) inv[i] = 1LL * (MOD - MOD / i) * inv[MOD % i] % MOD; fac[i] = 1LL * fac[i - 1] * i % MOD; ifac[i] = 1LL * ifac[i - 1] * inv[i] % MOD; } } }; inline int consecutive_lagrange(int k, int *y, int n){ std::vector<int> pre(n + 2), suf(n + 2); pre[0] = k; suf[n + 1] = 1; for (int i = 1; i <= n; i++) { pre[i] = mul(pre[i - 1] , add( k , -i )); } for (int i = n; i >= 0; --i) { suf[i] = mul(suf[i + 1], add( k, -i) ); } int fk = 0; for(int i = 0; i <= n; i++){ int sgn = ((n - i) & 1) ? -1 : 1; int res = mul(i > 0 ? pre[i - 1] : 1, suf[i + 1]); int P = mul( sgn , y[i] ); int Q = mul(ifac[i] , ifac[n - i]); fk = add( fk , mul( res, mul( P , Q ) ) ); } return fk; } signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif invInit init; int tc = 0; std::cin >> tc; while (tc--) { std::cin >> k >> a >> n >> d; // // f(n + 1) = f(n) + (n + 1)^k f[0] = 0; // k > 0 // // g(n) = sum_{i = 1}^{n} f(i) g[0] = 0; for (int i = 1; i <= k + 3; i++) f[i] = add( f[i - 1] , qpow(i, k) ); for (int i = 1; i <= k + 3; i++) g[i] = add( g[i - 1] , f[i] ); // // h(n) = sum_{i = 0}^{n} g(a + di) h[0] = consecutive_lagrange( mul(a, 1) , g, k + 2 ); for (int i = 1; i <= k + 3; i++) { h[i] = add( h[i - 1], consecutive_lagrange( add( a, mul( i , d ) ) , g, k + 2 ) ); } int h_n = consecutive_lagrange(n, h, k + 3); std::cout << h_n << "\n"; } return 0; }
8. 拉格朗日插值水题 3 :https://www.luogu.com.cn/problem/P3270
题意:G 班有
询问 G 班有多少种可能的分数情况。
题解:
数据范围似乎可以接受 DP ,于是尝试考虑 DP 做法。
设
考虑
-
考虑排名的分布情况。
若考虑了 门必修课,原 个人在前 门必修课被 B 神碾压的情况下第 门必修课可能超过 B 神,于是被 B 神碾压的人数可能变少。
原来被 B 神碾压的 个人有 个人不再被碾压,于是从原来被碾压的 个人中需要选 个人,这门必修课成绩需要大于 B 神,另外的人这门必修课小于等于 B 神。
此时第 门必修课成绩高于 B 神的位置还剩 个,于是从原来没被碾压的 个人中选 个人,这门必修课成绩需要大于 B 神,另外的人这门必修课小于 B 神。 -
考虑第
门课的排名确定后,每个排名上的人的分数分配情况。
枚举 B 神第 门课的可能成绩为 。
则在这门课中,有 个排名高于 B 神的人分数范围在 ,每个人有 种可能的分数。
个排名不高于 B 神的人分数范围在 [1, j] ,每个人有 种可能的分数。
关于同一个变量的高阶多项式加上低阶多项式不影响阶数,不需要真的使用二项式定理展开。
比较直观地可以只考虑能取到的最高次
令
于是得到转移方程
注意整理:
- 一个显然的枚举上界至少是
,即( ) - 原本被碾压的
个人现在不再被碾压,则这 个人至少需要满足的约束是这门课的成绩高于 B 神,即 。 - 现在能被碾压的人数严格上界是
,即 。
考虑 DP 数组的良序情况,第
组合数可以
于是转移复杂度为
view
#include <bits/stdc++.h> typedef long long ll; const int N = 210; const int CN = 110, CM = 110; const int MOD = 1E9 + 7; int add(int a, int b) { return ( 1LL * (1LL * a + b) % MOD + MOD) % MOD; } int mul(int a, int b) { return (1LL * a * b % MOD + MOD) % MOD; } int qpow(int a, ll n) { int res = 1; for (; n; a=mul(a, a), n>>=1) if(n&1) res=mul(res, a); return res; } int U[CM], R[CM], f[CM][CN], T[CM]; int inv[N], fac[N], ifac[N], y[N]; int n, m, k; struct invInit { invInit() { inv[1] = fac[0] = ifac[0] = 1; for (int i = 1; i < N; i++) { if (i > 1) inv[i] = mul(MOD - MOD / i, inv[MOD % i]); fac[i] = mul(fac[i - 1], i); ifac[i] = mul(ifac[i - 1], inv[i]); } } }; int comb(int n, int m) { if (n < 0 || m < 0 || n < m) return 0; return mul(fac[n], mul(ifac[n - m], ifac[m])); } int consecutive_lagrange(int k, int *y, int n) { int fk = 0; std::vector<int> pre(n + 2), suf(n + 2); pre[0] = k; suf[n + 1] = 1; for (int i = 1; i <= n; i++) pre[i] = mul(pre[i - 1], k - i); for (int i = n; i >= 0; --i) suf[i] = mul(suf[i + 1], k - i); for (int i = 0; i <= n; i++) { int sgn = ((n - i) & 1) ? -1 : 1; int res = mul( i > 0 ? pre[i - 1] : 1, suf[i + 1]); int P = mul(y[i], sgn); int Q = mul(ifac[i], ifac[n - i]); fk = add(fk, mul(res, mul(P, Q))); } return fk; } signed main() { #ifndef ONLINE_JUDGE freopen("IO/in", "r", stdin); freopen("IO/out", "w", stdout); #endif invInit __init__inv; std::cin >> n >> m >> k; for (int i = 1; i <= m; i++) { std::cin >> U[i]; } for (int i = 1; i <= m; i++) { std::cin >> R[i]; } for (int i = 1; i <= m; i++) { y[0] = 0; for (int j = 1; j <= n; j++) { int res = mul(qpow(add(U[i], - j), add(R[i], - 1)), qpow(j, add(n , - R[i]))); y[j] = add(y[j - 1], res); } T[i] = consecutive_lagrange(U[i], y, n); } f[0][n - 1] = 1; for (int i = 1; i <= m; i++) { for (int j = 0; j <= n - R[i]; j++) { for (int l = 0; l <= std::min(n - 1 - j, R[i] - 1); l++) { int res = f[i - 1][j + l]; res = mul(res, mul(comb(j + l, l), comb(n - 1 - j - l, R[i] - 1 - l))); res = mul(res, T[i]); f[i][j] = add(f[i][j], res); } } } std::cout << f[m][k] << "\n"; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】