算法
(枚举) $\ \ \mathcal O(n \log _ {10} n)$
首先考虑一下暴力,很容易写出暴力的代码:
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
if (check(a[i], a[j]))
ans ++ ;
其中check(a[i], a[j])
是判断a[i]
与a[j]
的拼接是否为k
的倍数。ans
存答案。
考虑优化。
首先最外面那层循环肯定是去不掉了,因为我们一定要枚举所有数字。
那就只能看能否去掉内层的循环,也就是考虑如何快速求出与a[i]
拼接起来为k
的倍数的数字
要与a[i]
拼接起来是k
的倍数,那首先要考虑a[i]
和a[j]
拼起来是个啥玩意。
观察到a[i]
与a[j]
拼起来的结果即为 $a[i] \times 10 ^ {\lfloor \log _ {10}{a[j]} \rfloor + 1} + a[j]$
那要$a[i] \times 10 ^ {\lfloor \log _ {10}{a[j]} \rfloor + 1} + a[j]$ 是 k
的倍数,也就是要 $a[i] \times 10 ^ {\lfloor \log _ {10}{a[j]} \rfloor + 1} \% k + a[j] \% k$ 是 k
的倍数。
然后注意到 $a[i] \times 10 ^ {\lfloor \log _ {10}{a[j]} \rfloor + 1} \% k$ 这个数字是小于k
的,而k
最大是 $10 ^ 5$。
也就是说,我们是可以将其储存起来的。
所以我们可以开一个cnt
数组,其中cnt[i][j]
记录在之前遍历过的所有数中,乘 $10 ^ i$ 后模 k
的结果为 j
的数的个数。
然后对于所有的a[i]
,累加 $cnt[\lfloor \log _ {10} a[i] \rfloor + 1][(k − a[i] \% k) \% k]$ 即可。
但这只能求出所有 $i < j$ 的合法的 a[i]
与 a[j]
的个数。
从前往后跑一遍,从后往前再跑一遍就可以辣~
时间复杂度
循环一遍 $1 \sim n$,的时间复杂度是 $\mathcal O(n)$ 的,
循环里面求 $\log_{10}x$ 的时间复杂度 $\mathcal O(\log _ {10} n)$,
所以总的时间复杂度是 $\mathcal O(n \log _ {10} n)$。
C++ 代码
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 100010;
int n, mod; // n 即题目中 n,mod 即上述 k
ll ans; // ans 存答案,由于最多会有 n ^ 2 中情况,所以需要开 ll
int a[N]; // a 存输入数据
int cnt[11][N]; // cnt 即上述数组 cnt
int log_10(int x) // 返回 1 + log10(x)
{
int res = 0;
while (x) x /= 10, res ++ ;
return res;
}
void work() // 从前往后跑一遍
{
for (int i = 0; i < n; i ++ )
{
ans += cnt[log_10(a[i])][(mod - a[i] % mod) % mod]; // 累加 cnt
for (int j = 0, power = 1; j < 11; j ++ ) // 将 a[i] 的 0 ~ 10 次方 % mod 的结果计入 cnt
{
cnt[j][power * 1ll * a[i] % mod] ++ ;
power = power * 10 % mod;
}
}
}
int main()
{
scanf("%d%d", &n, &mod);
for (int i = 0; i < n; i ++ )
scanf("%d", a + i);
work();
memset(cnt, 0, sizeof cnt); // 别忘了在第二次跑之前清空一下 cnt 数组
// 其实并不需要从前往后,从后往前写两遍。
// 只需要将数组反转一下,再从前往后跑一遍即可
reverse(a, a + n);
work();
printf("%lld\n", ans);
return 0;
}
ORZ,看视频看了六七遍,就是y总说的每个字都能听懂,连起来就不知道啥意思了,看了你的题解就是。。顿悟的那种感觉ORZ!
tql
大佬,累加cnt为啥不放在第二个for循环的下面呢
同问
题目中求的是对于所有 $i < j$ 的 $a[i]$ 和 $a[j]$ 的拼接,所以应该先类加上前面所有能拼上的数,再将这个数放入
cnt
中,即将累加放在第二个for
循环前面(mod - a[i] % mod) % mod后面这个%mod的作用是什么啊,mod - a[i] % mod不是已经小于mod了吗?
不一定,当
a[i] = mod
时,a[i] % mod
的值为0
,那么mod - a[i] % mod
的值就等于mod
。后面这个
% mod
就只是为了特判一下这种情况soga
tql
最好dongle的一个题解
#include [HTML_REMOVED]
#include [HTML_REMOVED]
#include [HTML_REMOVED]
#include [HTML_REMOVED]
using namespace std;
const int N=100010;
int n,k;
long long a[N];
int res;
int main()
{
cin>>n>>k;
for(int i=1;i<=n;i++)
cin>>a[i];
}
我跟你的想法一样,为啥啊,过不了
为什么我写的暴力一个也没过 大佬们 是思路有问题吗
二层循环里的j要初始化为j=i+1,不然就重复计数了
很详细
说说本菜鸡的理解:
抽风大佬已经证明:a[i]前面的数扩大倍数后%mod的余数与a[i]本身%mod的数相加为0,他们组合起来即为一组。
a[j]%k就是循环到的数的余数,而(k-a[i]%k)%k就是能组成一组的余数,凡是前面扩大倍数后%mod能为该数就算一组。(看了有点久hh)
那要a[i]×10⌊log10a[j]⌋+1+a[j]a[i]×10⌊log10a[j]⌋+1+a[j] 是 k 的倍数,也就是要 a[i]×10⌊log10a[j]⌋+1%k+a[j]%ka[i]×10⌊log10a[j]⌋+1%k+a[j]%k 是 k 的倍数,这是为什么
同问,本来是k的倍数,%k后那结果不就是0吗
!!!!!
ACwing大佬云集
喵呀
是模k还是模m哦??
是模m。。我写着写着就开始胡乱写了QAQ。。
理解理解 嘿嘿嘿
a[i]×10⌊log10a[j]⌋+a[j] 这里错了…比如 a[i]=23 a[j]=456 你这样算就是2300+456 当时实际上是23000+456
是的,已改
应该是log10加1次方吧 大佬
是的,已改
大佬 cnt[log10a[i]][(m−a[i]%m)%m] 这句话啥意思呀
根据定义:
那么有:
$\text{cnt}[\log _ {10} a[i]][\text{(m - a[i]%m)%m}]$ 表示在之前遍历过的所有数中,乘 $10 ^ {\log _ {10} a[i]}$ 后,模
m
的值为 $(m - a[i]) \text{ mod } m$ 的数字个数那么我们累加这个数,就是拼接在 a[i] 后面后,正好是 m 的倍数的数,那么对于每个
a[i]
累加过后,就是答案cnt存的数是不是拼接在a[i]前面呀
是的
大佬你好, 我用 power * a[i] * 1ll % mod 但是这样就会过不了,为啥呢
先乘
1ll
,将power
转成long long
类型,再乘上a[i]
,才起到了避免爆int
的效果如果先
power * a[i]
,这个值就已经爆int
了,再乘上1ll
只是把一个已经溢出(由于溢出,值会是错误的)的int
型整数转成了long long
类型,没有任何作用懂了,感谢大佬!
为什么要跑两次呢,先跑完cnt在统计数量能行吗
跑第一次只枚举出了所有 $A _ i A _ j\ (i < j)$ 的拼接方式,并没有枚举 $A _ i A _ j\ (i > j)$ 的情况
想明白了,(:з」∠),我发现我之前理解错了。如果跑完cnt再累加是会把AiAi的情况算上。感谢dalao