给定一个长度为 n 的数组 A1,A2,⋅⋅⋅,An。
你可以从中选出两个数 Ai 和 Aj(i 不等于 j),然后将 Ai 和 Aj 一前一后拼成一个新的整数。
例如 12 和 345 可以拼成 12345 或 34512。
注意交换 Ai 和 Aj 的顺序总是被视为 2 种拼法,即便是 Ai=Aj 时。
请你计算有多少种拼法满足拼出的整数小于等于 K。
输入格式
第一行包含 2 个整数 n 和 K。
第二行包含 n 个整数 A1,A2,⋅⋅⋅,An。
输出格式
一个整数代表答案。
数据范围
1≤n≤10^5,
1≤K≤10^10,
1≤Ai≤10^9
输入样例:
4 33
1 2 3 4
输出样例:
8
思路分析: 首先看数据量,就可以排除O(n2)做法了。十万的数据量意味着我们每个数只能枚举一次,也就说我们需要在把O(n2)的里面一个循环给优化成O(logn)或者O(n)。
回到题目,两个数目拼接可以看成是 A * 10x + B <= K,这里的x是与B的位数有关,观察等式B与和x成正相关,那么我们只需要用11个哈希表将 A * 10x 先存起来,然后枚举B即可。但是观察数据量,显然我们没办法直接把值映射到哈希表上面,因为数据太大了会爆空间。
接下来就到第二个难点了,通过转化等式变成 A *10x <= K - B。则可以发现,如果储存A的序列单调,并且等式也满足二段性。那么我们在哈希表预处理时将序列按单调存储,就可以在枚举B时二分查找A * 10x的值了。最后注意下题目数会非常大,所以记得开unsign long long。在查找完最后记得判重,如果l >= i 则包含自己,需要减一。至此题目拆分完毕,时间复杂度为O(nlogn)。
不了解二分的同学可以看这里
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef long long ll;
typedef unsigned long long ull;
ull hash_table[11][N];
ll a[N],k;
int n;
ll cal(ull q[],int idx,ll target)
{
if(q[0] > target) return 0;
int l = 0,r = n - 1;
while(l < r)
{
int mid = l + r + 1 >> 1;
if(q[mid] <= target)
l = mid;
else
r = mid - 1;
}
return l >= idx ? l : l + 1;
}
int main()
{
cin >> n >> k;
for (int i = 0; i < n; i ++ ) cin >> a[i];
sort(a,a + n);
for (int i = 0; i < n; i ++ )
{
ll t = a[i];
for (int j = 0; j < 11; j ++ )
{
hash_table[j][i] = t;
t = t * 10;
}
}
ll res = 0;
for (int i = 0; i < n; i ++ )
{
int len = to_string(a[i]).size();
res += cal(hash_table[len],i,k - a[i]);
}
cout<<res<<endl;
return 0;
}