ICPC 2019 银川 F - Function! 题解
ICPC 2019 银川 F - Function! 题解
更好的阅读体验戳此进入
(建议您从上方链接进入我的个人网站查看此 Blog,在 Luogu 中图片会被墙掉,部分 Markdown 也会失效)
题目链接
题意
对于给定函数:
对于给定的 $ n \ (n \le 10^{12}) $ 求如下式子的值
前置知识
反函数(逆函数)
对于函数 $ f(x) $,记 $ f^{-1}(x) $ 为 $ f(x) $ 的反函数。可以简单的将其理解为把 $ f(x) $ 的定义域与值域的映射关系调换一下就是 $ f^{-1}(x) $ 了。
对于本题的 $ f(x) $ 显然指数函数的反函数即对数函数,可以这样理解:
对于 $ f_a(x) = a^x $,可以通俗地理解为 $ x $ 为自变量,$ a^x $ 为因变量,调换后可以认为是 $ f{-1}_a(ax) = x $,令 $ \xi = a^x $,则有 $ f_a^{-1}(\xi) = \log_a^\xi $。
逆元
TODO
对于求逆元的方法,以及除数较小时如何不用逆元,这里不再赘述,以后有时间可能开个逆元的坑。
分析
简单的转换
由前置知识可以得出题目中的式子等价于如下式子:
Tips
值得一提的是,本体因为输入数据只有一个数,所以可以考虑用数据库的思想,理论上在空间限制内,可以通过暴力程序,将结果以初始化数组的形式打印到文件里,这样的做法可以完全避免范围内的 $ TLE $,但是当考虑到文件大小的限制 $ 100KB $ 和本地跑暴力程序效率的限制,最多只能跑到 $ 1 \times 10^4 $ 左右。
$ O(n^2) $ 做法
显然直接枚举 $ a, b $ 即可,理论上可以跑 $ 10^4 $ 以内的数据。
$ O(n) $ 做法
考虑题中式子的性质:
显然我们可以看出显然有如下性质 $ b \ge a $。
则显然有
即
类比这个性质我们考虑 $ \log_a^b $,可以发现如下性质:
即
通过这两个性质原式可以转化为如下:
这里还可以继续化简,不过那就是常数问题了,显然现在这个式子可以直接 $ O(1) $ 求。
这时如果将 $ a \le \sqrt{n} $ 的部分暴力求,时间复杂度即为 $ O((\sqrt{n})^2) = O(n) $,理论上可以过 $ 1 \times 10^8 $ 的点。
$ O(\sqrt{n} \log{n}) $ 做法
观察以 $ a $ 为底的对数函数图像:
显然对于 $ \lfloor \log_a^b \rfloor $,由于下取整的性质,显然可以根据值域,将定义域分为若干段,即将 $ b $ 分为如下几个区间:
显然对于每个区间的值域都有且只有一个值,即:
这样我们便可以以此为基础进行优化,将对应值乘上区间内值的数量在计算即可。
注意
整除
关于式子中的除法是否可以整除,可以从很多个角度来理解。
- 由于我们的式子是从整式推过来的,所以显然最后的结果一定是个整数,所以必定可以整除。
- 由于题中需要取模,所以考虑模运算一般都不考虑浮点数,故可不严谨地认为其可以整除。
- 通过数学严谨地证明,可以考虑将除数分解后分别讨论被除数是否存在其因数,一般通过枚举被除数某个项对除数的因子取模的值来证明。
带模除法
这部分很显然,具体做法不再赘述,将除法改为乘逆元即可。
log 精度问题
如果按正解做会发现我们根本不需要计算 log 的值,所以不会有这个问题,但是如果选择 $ O(n) $ 甚至更暴力的做法就需要考虑到这一点了。
很多时候我们喜欢用换底公式来简单地表示出任意一个 log 的值,但是这样的求法对于单次使用精度还可以接受,但是对于本题,会随着 n 的增大逐渐放大这样做法的精度误差(我模拟赛的时候就因为这个而 0pts 了)。
不过这个我并没有想到高效且正确的方法,类似 BitScanReverse 这种的奇淫巧计也都是 VS 里面的,OI 用不了,能想到的只有用 long double 或者 float128 之类的东西,或者干脆递归手写求 log。
就像我写的正解里面被我注释掉的那一段代码,如果那一段的思路没有假掉了的话,那么就也是 log 炸精度了。
对于取模
这道题里的 $ n $ 读入就已经是 $ long \ long $ 的范围了,所以用到的时候都要取模,并且管理好优先级,否则很容易计算过程中爆 $ long \ long $ 导致 WA。
AC Code
#define _USE_MATH_DEFINES
#include <bits/stdc++.h>
// #include <quadmath.h>
#define PI M_PI
#define E M_E
#define npt nullptr
#define MOD 998244353
/******************************
abbr
******************************/
using namespace std;
mt19937 rnd(random_device{}());
int rndd(int l, int r){return rnd() % (r - l + 1) + l;}
typedef unsigned int uint;
typedef unsigned long long unll;
typedef long long ll;
#define int ll
#define sqrt sqrtl
#define floor floorl
#define ceil ceill
#define log logl
#define sqrtN (ll)(floor((long double)(sqrt(n))))
#define logg(a, b) (ll)(floor(log((long double)b) / log((long double) a)))
int qpow(int a, int b){
ll ret(1), mul(a);
while(b){
if(b & 1)ret = ret * mul % MOD;
b >>= 1;
mul = mul * mul % MOD;
}
return int(ret);
}
const int inv_2 = qpow(2, MOD - 2);
const int inv_6 = qpow(6, MOD - 2);
ll CalFunc(ll n){
ll left = (n + 1) % MOD * (ll)inv_2 % MOD * ((sqrtN + n + 1)%MOD) % MOD * (ll(-sqrtN + n)%MOD) % MOD;
ll rightl = n % MOD * ((n + 1) % MOD) % MOD * ((2 * n + 1) % MOD) % MOD * inv_6 % MOD;
ll rightr = sqrtN % MOD * ((sqrtN + 1)%MOD) % MOD * ((2 * sqrtN + 1)%MOD) % MOD * (inv_6 % MOD) % MOD;
ll right = (rightl - rightr + MOD) % MOD;
return (left - right + MOD) % MOD;
}
template<typename T = int>
inline T read(void);
signed main(){
int N = read();
int ans(0ll);
for(int a = 2; a * a <= N; ++a){
// int tmpp(0);
// for(int k = 1; k < logg(a, N); ++k){
// int tmp = (qpow(a, k + 1) - qpow(a, k) + MOD) % MOD;
// tmpp = (tmpp + tmp) % MOD;
// }
// tmpp = ((N - qpow(a, logg(a, N)) + 1) % MOD + tmpp) % MOD;
// ans = (ans + tmpp * a % MOD) % MOD;
ll tmp(1ll);
for(int b = a; b <= N; b *= a, ++tmp){
ans = (ans + (min(b * a - 1, N) - (b - 1) + MOD) % MOD * tmp % MOD * a % MOD) % MOD;
}
}
ans = (ans + CalFunc(N)) % MOD;
printf("%lld\n", ans);
fprintf(stderr, "Time: %.6lf\n", (double)clock() / CLOCKS_PER_SEC);
return 0;
}
template<typename T>
inline T read(void){
T ret(0);
short flag(1);
char c = getchar();
while(c != '-' && !isdigit(c))c = getchar();
if(c == '-')flag = -1, c = getchar();
while(isdigit(c)){
ret *= 10;
ret += int(c - '0');
c = getchar();
}
ret *= flag;
return ret;
}
UPD
update-2022_08_25 初稿
update-2022_08_25 小优化