串联数字

串联数字

给定 n 个正整数 a1,a2,,an

我们规定将正整数 aiaj 串联是指将 aj 直接接在 ai 后面构成一个新整数。

例如,1234 串联得到 12343412 串联得到 3412

现在,给定一个正整数 k,请你计算有多少个有序数对 (i,j)(ij) 满足,aiaj 串联得到的整数能够被 k 整除。

输入格式

第一行包含两个整数 n,k

第二行包含 n 个正整数 a1,a2,,an

输出格式

一个整数,表示满足条件的有序数对的数量。

数据范围

6 个测试点满足 1n6
所有测试点满足 1n2×1052k1091ai109

输入样例1:

6 11
45 1 10 12 11 7

输出样例1:

7

输入样例2:

4 2
2 78 4 10

输出样例2:

12

输入样例3:

5 2
3 7 19 3 3

输出样例3:

0

 

解题思路

  这题之前有做过:整数拼接。不过k的取值范围扩大到了109,而且也不能用unordered_map,否则会TLE,必须要手写哈希表。因此完全可以按照那题的思路来做,只不过哈希表要自己手写实现。

  讲一下y总思路。如果我们枚举到了ai,现在看一下前面有多少个aj, (0j<i)满足kajai¯,关键就在于如何快速判断前面有多少个aj能够满足这个条件。

  kajai¯等价于aj×10t+ai0  (mod k),即aj×10tai  (mod k),其中tai在十进制下的位数。当枚举到ai时,t就是个定值,因此问题就变成了前面有多少个aj10t后模kai。可以想到用哈希表来记录之前出现过的aj×10t mod kt的范围很小,为110,因此每次枚举完ai后,存一下ai101,102,,1010k的值,在哈希表中该值出现的次数加1。因此当枚举到ai时,求一下ai的位数t,然后直接查哈希表得到前面有多少个数乘10tkai。哈希表有两个关键字,一个是余数r,一个是位数t,为了方便把这个两个数组合成一个long long,即r×100+t来作为关键字。

  还有一点就是这题要枚举到aiaj后面和前面两种情况,上面的做法只枚举到aiaj后面,第一遍求完后还需要从后往前枚举来求aiaj前面的情况,其实只需要把数组翻转然后再套第一遍的做法从左到右扫描就好了。

  更新:补充对aj×10tai  (mod k)的理解,这里的tai的位数。当枚举到ai时,此时ai是定值,t也是定值。这时我们本来是要枚举所有的aj, (ji),看看有多少个aj满足aj×10tai  (mod k),但我们发现一开始可以使用哈希表来预处理统计,使得判断满足条件的时间复杂度为O(1)。那么预处理统计什么东西呢?对于判断条件。此次就只有aj是一个变量,其他都是常量,即需要看有多少个aj在乘上10tk后的值恰好为ai%k。因此一开始需要预处理统计出数组的每个数乘上10tk后的值的出现次数。虽然说在枚举到ait是个定值,但不同的ai的位数会不同,而t的取值范围是110,因此预处理时每个数都要乘上10的若干个次方。然后在枚举到ai时,cnt[t][ai%k]就是答案(当然还需要进行特判)。

  同理ai×10taj  (mod k),看成ajai×10t  (mod k),一开始预处理统计每个数xx%k的出现次数。

  关键点就是预处理变量的部分,之前一直不是很理解。

  AC代码如下:

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 typedef long long LL;
 5 
 6 const int N = 2e5 + 10, M = 1e7 + 3;
 7 
 8 int n, m;
 9 int a[N];
10 LL h[M];
11 int cnt[M];
12 
13 int find(int r, int t) {
14     LL x = r * 100ll + t;   // r是余数,t是位数
15     int k = x % M;
16     while (h[k] != -1 && h[k] != x) {
17         if (++k == M) k = 0;
18     }
19     
20     return k;
21 }
22 
23 LL solve() {
24     LL ret = 0;
25     memset(h, -1, sizeof(h));
26     memset(cnt, 0, sizeof(cnt));
27     for (int i = 0; i < n; i++) {
28         int r = (-a[i] % m + m) % m;
29         int t = 0, x = a[i];
30         while (x) {
31             t++;
32             x /= 10;
33         }
34         ret += cnt[find(r, t)];
35         
36         for (int j = 1, x = 10; j <= 10; j++, x = x * 10ll % m) {   // 统计(a[i]*10^k)%m的出现次数
37             int t = find(1ll * a[i] * x % m , j);
38             if (h[t] == -1) h[t] = 1ll * a[i] * x % m * 100ll + j;
39             cnt[t]++;
40         }
41     }
42     
43     return ret;
44 }
45 
46 int main() {
47     scanf("%d %d", &n, &m);
48     for (int i = 0; i < n; i++) {
49         scanf("%d", a + i);
50     }
51     
52     LL ret = solve();   // 先正着求一遍
53     reverse(a, a + n);  // 反转数组
54     ret += solve();     // 再正着求一遍
55     
56     printf("%lld", ret);
57     
58     return 0;
59 }
复制代码

  我当时的思路是ai在前面而aj在后面,对于一个ai,本质是枚举所有aj, (ij),求有多少个aj满足ai×10taj  (mod m),其中这里的taj的数位。因此可以预处理用哈希表来统计每个ai%k出现的次数。然后枚举ai,对于每个aik都从1开始枚举到10,查哈希表统计ai×10k%m出现的次数,也就是aj(数位为k)的满足条件个数。

  AC代码如下:

复制代码
 1 #include <bits/stdc++.h>
 2 using namespace std;
 3 
 4 typedef long long LL;
 5 
 6 const int N = 5e6 + 3;
 7 
 8 int a[N], cnt[11][N], len[N];
 9 int h[N];
10 
11 int find(int x) {
12     int k = x % N;
13     while (h[k] != -1 && h[k] != x) {
14         if (++k == N) k = 0;
15     }
16     return k;
17 }
18 
19 int main() {
20     int n, m;
21     scanf("%d %d", &n, &m);
22     memset(h, -1, sizeof(h));
23     for (int i = 0; i < n; i++) {
24         scanf("%d", a + i);
25         int t = a[i];
26         while (t) {
27             len[i]++;
28             t /= 10;
29         }
30         
31         t = (1ll * -a[i] % m + m) % m;
32         int x = find(t);
33         if (h[x] == -1) h[x] = t;
34         cnt[len[i]][x]++;
35     }
36     
37     LL ret = 0;
38     for (int i = 0; i < n; i++) {
39         LL t = a[i];
40         for (int j = 1; j <= 10; j++) {
41             t = t * 10 % m;
42             ret += cnt[j][find(t)];
43         }
44         
45         t = a[i];
46         for (int j = 0; j < len[i]; j++) {
47             t = t * 10 % m;
48         }
49         if (t == (1ll * -a[i] % m + m) % m) ret--;  // 如果有a[i]*10^t = -a[i] (mod m),这种情况下i==j,要特判掉
50     }
51     
52     printf("%lld", ret);
53     
54     return 0;
55 }
复制代码

 

参考资料

  AcWing 4611. 串联数字(AcWing杯 - 周赛):https://www.acwing.com/video/4297/

posted @   onlyblues  阅读(82)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示