原题链接
思路:
拼接两个整数比如12
和345
,得出的12345
就等于12 ✖ 10 ^ 3 + 345
,34512
就等于345 ✖ 10 ^ 2 + 12
。
因此本题就相当于求Ai
和Aj
其(Ai + Aj * 10 ^ len(Ai)) % k == 0
这一等式,其中len(Ai)
是Ai
的位数。
将上述等式转化为(Aj * 10 ^ len(Ai)) %k == -Ai % k
;
因此本题就成了首先枚举Ai
,然后求有几个Aj
其乘以10的len(Ai)次方 % k
== -Ai % k
。
因此,创建一个二维数组s[i][j]
,这个数组就表示(某个数 ✖ 10 ^ i)% k == j的数量;(数组会预处理)
所以本题就转换为每次枚举一个Ai
,然后计算出其%k
的数来,然后再算出Ai
的位数len
,然后去寻找s[len][Ai % k]
的大小,就能得知有多少个数满足条件,同时也就是答案数。
本题步骤:
(1)枚举整体数组来预处理s
数组。
(2)再依次枚举这个数组中的每个数Ai
。然后在上述预处理之后的数组中找出一个或多个数字,
这一个或多个数字Aj
满足(Aj * 10 ^ len(Ai)) == -Ai % k
。
(3)其中每次算结果的时候需要判重。因为之前在计算预处理数组时候一定会枚举到自己本身,并将自己本身 ✖ 10的j次方 % k
存入到s
数组中。因此在这次计算结果的时候一定就要判断有无自己,有则结果减一。
详细解释某些步骤(先看代码再看这里吧):
(1)在预处理s数组的时候:
//此时举例枚举到的数字是a[i]
long long t = a[i] % k;
for(int j = 0; j < 11; j ++)//因为题目中给出的最大数是10^9
{
s[j][t] ++;
t = t * 10 % k;
}
第一行的t = a[i] % k
就是计算a[i] * 10 ^ 0 % k
;下面的 t = t * 10 % k;
意思就是再算a[i] * 10 ^ j % k
;其实举个数看一下就能明白。
比如$a[i] == 345$,$k == 2$。
(1)345 ✖ 10 ^ 0 % 2 == 1
,345 ✖ 10 ^ 1% 2 == 0
,345 ✖ 10 ^ 2 % 2 == 0
。
(2)利用 t = t * 10 % k
计算就是:首先t = 345 % 2 == 1
, 随后t = t ✖ 10 % 2 = 1 ✖ 10 % 2 == 0
,t = t ✖ 10 % 2 = 0 ✖ 10 % 2 == 0
。
可见是一一对应的。
(2)循环数组计算结果的时候:
for(int i = 0; i < n; i ++)
{
LL t = a[i] % k;
int len = to_string(a[i]).size();//将这个数字转化为字符串,再判断转换后的字符串的位数就等于这个数字本身的位数
res += s[len][(k - t) % k];
这里的res += s[len][(k - t) %k];
中的(k - t) %k
的意思:因为这里是计算结果的时候枚举,
而根据上面我们推出来的的等式(Aj * 10^len(Ai)) %k == -Ai % k
,此时我们需要计算-Ai % k
。
但是题里面的Ai
都是正数,而在C++中一个负数摸以一个数其答案都是负数,但是在数学中余数都必须是正数(定义)。
就像在C++中-5 % 3 == -2
,而在数学中答案应该是1
。
为什么会是1呢,因为在百度百科的“取模运算”的词条里有这样的定义(点击here查看定义) :
给定一个正整数p,任意一个整数n,一定存在等式 :n = kp + r ;
其中 k、r 是整数,且 0 ≤ r < p,则称 k 为 n 除以 p 的商,r 为 n 除以 p 的余数。在我们这里p是3,n是-5,要想r是正的,那么就有-5 = -2 * 3 + 1,因此我们算出了余数为1。
举例当k = 3
时候,我们枚举到了一个数Ai
是5
,但我们希望求-5 % 3
的值,而根据上述我们得知-5 % 3 == 1
,那怎么利用Ai == 5
求出这个数呢,此时就有一个公式:t = (k - (Ai % k)) % k
。
利用这个公式就可以实现了:t=(3 - (5 % 3)) % 3 == 1
。
C++ 代码
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long LL;
const int N=100010;
int s[11][N];//表示某个数*10^i%k==j的数量
int n;//表示将要输入的n个数
LL a[N];//存放n个数
int k;//表示k倍
LL res;//表示结果
int main()
{
//1.输入数据
cin >> n >> k;
for(int i = 0; i < n; i ++)
cin >> a[i];
//2.预处理s数组
for(int i = 0; i < n; i ++)
{
LL t = a[i] % k;
for(int j = 0; j < 11; j ++)//因为题目中给出的最大数是10^9
{
s[j][t] ++;
t = t * 10 % k;
}
}
//3.循环数组计算答案
for(int i = 0; i < n; i ++)
{
LL t = a[i] % k;
int len = to_string(a[i]).size();//将这个数字转化为字符串,再判断转换后的字符串的位数就等于这个数字本身的位数
res += s[len][(k - t) % k];
//4.判重
LL r = t;
while(len--) r = r * 10 % k; //等价于求a[i]乘以10^len的余数,同上面的预处理求法一样
if(r == (k - t) % k) res --;
}
//5.输出答案
cout << res << endl;
return 0;
}
转化成字符串求位数,妙
判重有点没听懂,有大佬讲一下吗QAQ
为啥这样就是重复了
因为他本身就的乘于1ei次方也符合(k-t)%k的话,相当于一个数接上自身然后为k的倍数,这种情况就重复了
为啥会接上自身呢qaq
因为Ai可能会和刚开始创建Aj的哈希表中的Aj相等
当Ai和Aj相等的时候,相当于Ai接上Ai,也就是满足这个式子Ai * 10 ^ len(Ai) == Ai
相同的俩个吗
相同的一个,相等的两个是允许的,但是自己不能和自己接
一堆人是判重没看懂,我tm是t = t * 10 % k;没看懂
紫皮书里有,数论那里幂取模
就像一个常识一样,见多了,自己举个例子就懂了
终于看懂了!写的很好呜呜
感谢!!
(Aj * 10 ^ len(Ai)) == -Ai % k这个式子为啥成立
大佬们谁能说一下判重那个啊😩😩
满足条件的 Aj 和 Ai 不是要使等式
A[j] ^ (len(A[i])) % k == - A[i] % k
成立嘛,但是在求S数组的过程中,可能会找到 A[i] 本身,使等式变成A[i] ^ (len(A[i])) % k == - A[i] % k
仍然成立,但是题目里说了 :A[i] != A[j]
, 所以得判重判重的过程就是求使这个等式满足的
A[i]
,也就和面的预处理一样了跪谢跪谢,之前在评论区看到说“r是(Aj * 10 ^ len(Ai)) % k的余数,(k - t) % k是-Ai % k的余数”,我寻思r应该是(Ai * 10 ^ len(Ai)) % k的余数吧,脑子就是转不过来弯,终于懂了,谢谢谢谢谢谢😊😊
有一点我还是不太理解,百科中下面说-7%4=-3,余数并非大于等于0.而余数的百度百科中所给的余数计算公式为a - ⌊a / b⌋ * b(其中a为除数,b为被除数)计算出来的结果为1,为什么会这样???
预处理时j为什么要从0开始呢 len(a[j])不会等于0吧 j从1开始就错了 求解
因为第一个t进来的时候是自己本身,也就是10^0次方,所以得从0开始
主要是len不可能等于0吧,按理说是第0行根本用不到哇
s[j][t] ++;
t = t * 10 % k;
你把这俩行代码换一下就用不到0了,因为刚开始进来的时候就相当于是t*10^0,所以0还是有意义的
感谢,两行换一下,然后 j 从1开始,好理解多了。
请问一下判重的时候直接求a[i]乘以10^len然后最后取余就出错,为啥呀
ll q = a[i];
while(len–)q = q*10;
if(q%k == (k-a[i]%k)%k)result–;
猜它越界但是压根就没有越界啊
一键三连就看你的看懂了,棒棒
第一步怎么转换的我愣是想了半个月,原来右边求的是正余数。Orz
为啥j到10而不是j到9啊,不是最多1e9吗
因为这里的j是对于Aj的位数来说的,而Aj位数最大是10位数,10^9嘛,最多有10个位数
谢谢
求问为什么要%k有点没看懂
我也想问
大赞!
判重哪里还是有点不懂 为什么 r == (k - t) % k 就说明加重复了?
可能应该是(哈希)模拟散列表里的开放寻址法吧
r是(Aj * 10 ^ len(Ai)) % k的余数,(k - t) % k是-Ai % k的余数,你细品。
就这块判重想了十来分钟都没明白,看了评论就慢慢明白了
不是相等就重复了,只是相等说明a[i]这个数比较特殊,会导致res多加一个一,因此相等的时候需要res–
品不出来咋办😥😥
有点小瑕疵,前面的每一步都要%k,不是乘完了再%k
r应该是(Ai * 10 ^ len(Ai)) % k的余数吧,然后(k - t) % k是-Ai % k的余数,啊啊啊啊,大半夜的脑袋要炸了😩😩
t = t ✖ 10 % 2 = 1 ✖ 0 % 2 == 0,t = t ✖ 10 % 2 = 0 ✖ 0 % 2 == 0。
应该是t = t ✖ 10 % 2 = 1 ✖ 10 % 2 == 0,t = t ✖ 10 % 2 = 0 ✖ 10 % 2 == 0。
谢谢谢谢改了。
答主太细心了,好题解!
-5 = 2 * 3 + 1这里有问题
应该是 -5 = -2 * 3 + 1
好的谢谢啦,已改正。