题目
给定一个长度为 n 的数组 A1,A2,⋅⋅⋅,An。
你可以从中选出两个数 Ai 和 Aj(i 不等于 j),然后将 Ai 和 Aj 一前一后拼成一个新的整数。
例如 12 和 345 可以拼成 12345 或 34512。
注意交换 Ai 和 Aj 的顺序总是被视为 2 种拼法,即便是 Ai=Aj 时。
思路:
我们先从直接暴力的方法开始
for(int i = 1; i <= n; i ++)
for(int j = 1; j <= n; j ++)
if(check(i, j)) ans ++;
check函数是用来检查第i个数和第j个数能不能被k整除 ( i 放在 j 后)
如果
a
[
j
]
∗
1
0
整
数
a
[
i
]
的
位
数
+
a
[
i
]
a[j] * 10 ^ {整数a[i]的位数} + a[i]
a[j]∗10整数a[i]的位数+a[i]能被 k 整除就为 true
现在开始优化
因为外层循环没办法去掉,所以我们需要快速判断出当前这个数在序列里又多少个能被k整除的序列
又因为满足题意的条件为
(
a
[
j
]
∗
1
0
l
e
n
i
+
a
[
i
]
)
(a[j] * 10 ^ {leni} + a[i])
(a[j]∗10leni+a[i]) % k = 0
=
>
a
[
j
]
∗
1
0
l
e
n
i
=> a[j] * 10 ^ {leni}
=>a[j]∗10leni % k = - a[j] % k
所以我们如果能将所有a[j]预处理,用一个cnt数组来储存当前位数时当前余数的个数,那么我们在处理a[i]时,只需要知道a[i]的位数,就可以得到对应符合条件的个数
下面看代码理解
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 1e5 + 10;
LL a[N];
LL b[11][N];
int main()
{
int n, k;
cin >> n >> k;
for(int i = 1; i <= n; i ++)
cin >> a[i];
// 将所有情况预处理, 打表,下面直接查找时间就成了O(1)
for(int i = 1; i <= n; i ++)
{
int t = a[i] % k;
for(int j = 0; j < 11; j ++)
{
b[j][t] ++;
t = t * 10 % k;
}
}
LL ans = 0;
for(int i = 1; i <= n; i ++)
{
int len = to_string(a[i]).size();
int t = a[i] % k;
ans += b[len][(k - t) % k]; // a[j] * 10 ^ (len) % k = -a[i] % k; 这里使用了一个等价,且-a[i]<0,则%k的结果也为负数,用(k-a[i])%k,就会转化为a[i]%k
LL r = t;
while(len --) r = r * 10 % k; // 除去i和j相等的情况
if(r == (k - t) % k) ans --;
}
cout << ans << endl;
return 0;
}