拼数

题目大意:EndSaH 有 n 个数 a 1...n ,他打算选出这些数中的两个数进行拼接。一次拼数操作指的是将 x,y 两个正整数视作数字串,x 在前 y 在后拼接成一个新数字串,该新数字串所表示的正整数即这次拼数操作的结果。例如将 1234 与 56 拼数,将得到结果 123456。注意拼数操作是有顺序的,如拼接 56 与 1234 会得到 561234。但不知道为什么,EndSaH 一点也不喜欢 k 的倍数。他很好奇一个问题:有多少数对 (i,j)(1 ≤ i,j ≤ n,i ̸= j) 满足对 ai 与 aj 进行拼数操作后,结果不是 k 的倍数?由于他太菜了实在不会,只能向你求助了。

虽然我没有做对,但是非常喜欢的一道题,因为这道题思想太巧妙了

首先是套路:单步容斥,把问题转化成求多少对数是k的倍数

然后发现x与y拼数的实质是\(x \times 10^{log_{10}y + 1} + y\),只要左边的数字与右边的数字加起来(对k取模之后)为k或者0即可

所以就有了如下做法:

先预处理一个桶mp[i][j]表示满足一个数乘上10的i次方后对k取模的结果为j的数的个数

然后考虑枚举每一个数字,O(1)查询他的另一半是否存在

注意要减去自己对自己的贡献...

就是自己找到自己会变成的模样,提前减一,事后再加回来

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <bits/stdc++.h>
#define int long long
#define ll long long
using namespace std;

const int N = 2e5 + 2;

map<int, int>mp[12];
int n, k, maxv, res;
int a[N];

inline int div(int x)
{
int ret(0);
while (x) ++ ret, x /= 10;
return ret;
}

signed main()
{
int i, j;
n = read(), k = read();
for (i = 1; i <= n; ++ i) a[i] = read(), maxv = max(maxv, div(a[i]));
for (i = 1; i <= n; ++ i)
{
int tmp = a[i];
for (j = 1; j <= maxv; ++ j)
{
tmp = tmp * 10 % k;
++ mp[j][tmp];
}
}
for (i = 1; i <= n; ++ i)
{
int tmp = a[i], nod = div(tmp);
for (j = 1; j <= nod; ++ j) tmp = tmp * 10 % k;
-- mp[nod][tmp];
if (a[i] % k) res += mp[nod][k - a[i] % k];
else res += mp[nod][0];
++ mp[nod][tmp];
}
put(n * (n - 1) - res);
return 0;
}

后记:

思想极其棒的题目

要注意以后再碰到这样"拼数字对某个数字取模统计多少个符合条件单步容斥"要有思路