Solution
我们发现,题目中要求的"把a接到b前面"实际上等于 a ∗ 1 0 l o g 10 b + b a*10^{log_{10}b}+b a∗10log10b+b。于是我们自然想到预处理出 a i ∗ 1 0 x m o d    k a_i*10^x \mod k ai∗10xmodk,显然 x x x不会超过10。然后对于每个 a i a_i ai,查询有多少数接到自己前面可以被 k k k整除。
预处理和枚举 a i a_i ai的 O ( n ) O(n) O(n)复杂度是不可避免的,那么我们就考虑如何加快询问速度。
最容易想到的是,我们对于每个
x
x
x,把
a
i
x
m
o
d
  
k
a_i^{x}\mod k
aixmodk插入到一个map
中。这样最多开10个map
,并且每次查询只需
l
o
g
2
n
log_2n
log2n的时间,理论上可以通过此题。
然而到这一步的时候,我就看见同机房巨佬的
m
a
p
map
map被卡到了自闭,最后用了unordered_map
才勉强卡过(话说unordered_map
是不是需要c++11啊,哪位大佬科普一下)。然后才想起来
m
a
p
map
map有巨大的常数,于是我们想进一步优化。
我们发现,这里的
m
a
p
map
map实际上大材小用了,我们只需要简单的插入和查询,并不需要容器有序,于是我们可以自然的想到用unordered_map哈希表进行优化。虽然哈希表时间复杂度比较玄学,但再怎么样也不会比一只
l
o
g
log
log再加大常数的map
慢。而且哈希表复杂度几乎可以看做常数级别。
所以我们就可以愉快地水掉一道紫题啦!!
Code
#include <bits/stdc++.h>
#define ll long long
#define MAX 200005
#define P 10007
using namespace std;
ll n, k;
ll a[MAX], p[20], lg[MAX];
struct hash_map {
int head[MAX], Next[MAX], cnt;
ll val[MAX], tot[MAX];
hash_map(){
cnt = 0;
memset(head, 0, sizeof(head));
memset(Next, 0, sizeof(Next));
memset(val, 0, sizeof(val));
memset(tot, 0, sizeof(tot));
}
void insert(ll x) {
ll k = x%P;
for(int i = head[k]; i; i = Next[i]) {
if(val[i] == x) {
tot[i]++;
return;
}
}
cnt++;
Next[cnt] = head[k];
head[k] = cnt;
val[cnt] = x;
tot[cnt] = 1;
}
ll find(ll x) {
ll k = x%P;
for(int i = head[k]; i; i = Next[i]) {
if(val[i] == x) {
return tot[i];
}
}
return 0;
}
}mp[15];
int main() {
cin >> n >> k;
p[0] = 1;
for(int i = 1; i <= 10; i++) {
p[i] = p[i-1]*10%k;
}
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
ll tmp = a[i];
while(tmp) {
lg[i]++;
tmp /= 10;
}
}
for(int i = 1; i <= 10; i++){
for(int j = 1; j <= n; j++){
mp[i].insert(a[j]%k*p[i]%k);
}
}
ll t, ans = 0;
for(int i = 1; i <= n; i++){
t = ((k-a[i])%k+k)%k;
ans += mp[lg[i]].find(t);
if((a[i]%k*p[lg[i]]%k+a[i]%k)%k == 0){
ans--;
}
}
cout << ans << endl;
return 0;
}