2020 多校第九场 E https://ac.nowcoder.com/acm/contest/5674/E
给定 ,求:
TL;DR:找找规律乱搞几下就出来了,WA 到怀疑人生就果断上 。
大家好,我又来丢人了。已经尽我所能地详细写了,希望 我公式没敲错 能帮到大家。
这里假定你能看懂题目的式子并把暴力做法给写出来。当然了暴力是不可能过的,但可以使用暴力模拟一下样例,寻找一些规律。
例如,在模拟样例二的时候,显而易见地 可以观察到:
- 对于 ,只要 , 就一定成立;
- 同理对于 ,只要 , 就一定成立;
- 同理对于 ,,不写了不写了自己算几项就有了。
可以猜想到可能会和 和 的因数有一定的联系,不妨就顺着这个思路探究一下这个恒成立的触发条件到底是什么,从而优化我们的暴力做法。
前置知识:
- 快速幂 - 就是用 的时间复杂度算出 。
- 算术基本定理 - 任何一个大于 的自然数 ,都可以分解为有限个质数的乘积。
- 利用算术基本定理计算两个数的最大公因数,这里随便找了一篇文章供参考。
现在我们就利用算术基本定理计算 ,我们首先把 和 拆分成下面的形式:
其中,对于 ,有:
- 是质数;
- ,;
- 。
显然对于任意 和 ,根据算术基本定理,必定存在符合要求的写法。
那么 和 就可以写成下面的形式:
不难得到下面的式子:
如果看公式难以理解,这里举个栗子,假设要你计算 :
因为有:
所以有:
那么就可以算出结果:
现在我们可以把刚刚得出的式子代入到题目里:
然后做一些等价转换,目的是在式子中找到我们接下来需要关注的部分。
首先,根据乘法交换律:
实际上就是先枚举 ,然后再枚举 和 。
接下来,根据同底数幂相乘,底数不变指数相加:
我们在这里停一下,尝试口胡一下现在我们应该怎样计算这个式子:
首先对 和 进行质因数分解,并根据分解结果初始化 、 和 三个数组。要注意这些数组都不会再被更新了,也意味着对于每一个 , 和 都是确定的值。然后对于每一个 ,我们枚举 ,然后计算:
这一部分就是我们接下来要关注的部分,这是因为:
这是一个关于 的分段函数,而且其中一段是常函数,另一段是一次函数,这意味着我们可以往分段求和的方向想。
考虑临界值的位置:
第一种情况:,那么有:
第二种情况:,那么有:
第三种情况:,那么有:
如果还是对求和过程有疑问,其实这就是等差数列求和嘛!首项加末项乘以项数除以二。
当然在编程的时候可能希望把三种情况归类成一种情况,是可以的:
直接将边界调整到 范围内:
然后套用第二种情况的式子,你会发现将边界值调整后,这个式子同样适用于情况一和情况三。
复杂度足够应付这个数据范围了,那就到此为止。
接下来就是编程实现,刚才已经口胡过做法了(请看上面的粗体字)。有几个地方可能需要考虑的:
- 或 ,无论 是什么,很显然答案必定是 ,其实可以不特判的。
- ,在计算边界值 的时候不考虑这个可能会有除零错误,但是很显然这种情况下 不会影响答案。
- 数据范围,掐指一算可能会爆 ,那就使劲开 。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MOD = 998244353; ll a, b, c, d, x, y; map<ll, pair<ll, ll>> fac; ll qpow(ll a, __int128 b) { ll ans = 1, base = a; while (b) { if (b & 1) ans = ans * base % MOD; base = base * base % MOD; b >>= 1; } return ans; } void update_factor(ll x, bool loc) { ll temp_x = x; for (ll i = 2; i * i <= x; i++) { while (temp_x % i == 0) { loc == 0 ? fac[i].first++ : fac[i].second++; temp_x /= i; } } if (temp_x != 1) { loc == 0 ? fac[temp_x].first++ : fac[temp_x].second++; } } int main() { cin >> a >> b >> c >> d >> x >> y; update_factor(x, 0); update_factor(y, 1); ll ans = 1; for (auto k : fac) { if (k.second.second == 0) { continue; } __int128 curcnt = 0; for (int i = a; i <= b; i++) { ll edge = k.second.first * i / k.second.second; edge = min(d, max(c - 1, edge)); curcnt += (d - edge) * k.second.first * i; curcnt += k.second.second * (edge + c) * (edge - c + 1) / 2; } ans = ans * qpow(k.first, curcnt) % MOD; } cout << ans << endl; }
大家一起加油鸭!可以随意转载本题解(注明来源),最后附上本文 Markdown 源码。