暴力:
计算每个数的b次幂——O(b),将算得的数加和 ——O(a),N组样例O(N),所以暴力解法的时间复杂度为
O(a × b × n)。即1e9 * 1e9 * 1e2 = 10^20,大概要大概要 10^12s,也就是大概31710年吧(雾)。
我们开始进行优化,首先快速幂可以很优秀的优化掉乘法的过程,将O(b)的复杂度优化为O(log b)。
优化之后时间复杂度为O(a * log b * N),大概是 1e12,也就是1000s,还是很大呜呜呜0w0。
int ksm(int a, int b, int mod) //快速幂
{
if(b == 1) return a;
if(b & 1) return a % mod * ksm(a, b - 1) % mod;
else
{
int x = ksm(a, b / 2) % mod;
return x * x % mod;
}
}
可以发现即使快速幂能够极大程度的优化乘法的过程,但是我们的算法依然很慢很慢。
这里可以发现需要模的数字很小,仅仅1e4,所以我们可以从这个地方下手。我们可以发现对一个数a,都有 (a + 10000) % 10000 ≡ a % 10000,根据同余的性质,有(a + 10000)^b % 10000 ≡ a^b %10000。对于每个大于10000的数a,a^b和(a % 10000) ^ b相等,也就是说我们已经在之前计算1到10000内的数的b次幂的时候就已经计算过a^b。
但是我们需要注意,a是一个很大的数字,最大为1e9,会出现重复计算多次相同的值的情况。
例如,a为1e6 + 10,那么实际上我们计算1 ~ 10000的b次幂的加和需要计算三次,然后还需要加上1 ~ 10的b次幂的加和。
也就是说,我们最后需要的
得数是 add (1-10000) * (a / 10000)+ add (1 - a).
我们发现我们可以用一个数将1-10000的和记录下来,但是我们之后还需要计算 1 - a的和,实际上这个数字已经在我们之前计算1-10000的和的时候就计算过,所以我们可以用前缀和数组来记录这个数字,进行优化。
最后可以用O(1)的复杂度进行公式运算得出答案。
我们来看看现在的时间复杂度:
预处理复杂度:O(log b * 1e4 * N),
求解复杂度: O(1)。
所以总时间复杂度为1e6 ~ 1e7,接近1s,可以求解。
通过快速幂,同余和前缀和,我们将一个10 ^ 20的算法优化到了10 ^ 7!!!
AC代码:
#include<bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f30f
#define mod 10000
#define endl '\n'
#define IOS std::ios::sync_with_stdio(false);cin.tie(0)
using namespace std;
const int N = 1e4 + 10;
int sum[N];
int ksm(int a, int b)
{
if(b == 1) return a;
if(b & 1) return a % mod * ksm(a, b - 1) % mod;
else
{
int x = ksm(a, b / 2) % mod;
return (x * x) % mod;
}
}
void solve()
{
int a, b;
cin >> a >> b;
for(int i = 1; i <= N; i ++)
sum[i] = (sum[i - 1] % mod + ksm(i, b) % mod) % mod;
cout << (a/10000 * qz[10000] + qz[a % 10000]) % mod << endl;
}
int main()
{
IOS;
int t;
cin >> t;
while(t --)
{
solve();
memset(qz, 0, sizeof qz);
}
return 0;
}