第十一届蓝桥杯省赛-整数拼接

第十一届蓝桥杯省赛-整数拼接

  • 题目
    • 暴力做法 O(n2)
    • 优化做法 O(nlog10(n))

题目

第十一届蓝桥杯省赛-整数拼接_第1张图片

暴力做法 O(n2)

题目意思很清楚,从n个数中任意选两个不重复的数,将其拼接起来,将拼接后的数对K求余,如果余数为零则代表是K的倍数。算法复杂度:从n个数中选2个数就是An2,也就是n*(n-1),很明显是n2的复杂度。整体数据范围是105很明显会超时,但是可以过掉30%的数据,即103范围内的数据。

#include 
#include 
using namespace std;
const int nm = 1e5 + 10;
long long arr[nm];
int main()
{
     
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0); 
	int n = 0,k = 0,cnt = 0;
	cin >> n >> k;
	for(int i = 0;i<n;i++) 
	cin >> arr[i];
	for(int i = 0;i<n - 1;i++)
	{
     
		for(int j = i+1;j<n;j++)
		{
     
			int a = arr[i],b = arr[j];
			if(a%k == 0&&b%k == 0) 
			{
     
			    cnt+= 2;
			    continue;
			}
			int a_num = log10(a),b_num = log10(b);//求出两个数的位数
			long long cur_a = b*pow(10,a_num + 1) + a,cur_b = a * pow(10,b_num + 1) + b;
			if(cur_a%k == 0) cnt++;
			if(cur_b%k == 0) cnt++; 
		}
	}
	cout <<cnt << endl;
	return 0;
} 

优化做法 O(nlog10(n))

首先,要想知道两个数拼接后的数能被K整除,必须遍历到每个数,即外循环n是逃不掉的,那如何将内循环的复杂度O(n)降下来呢。先用数学公式,将表达式写出来,假如找到了两个数A[i],和A[j],i < j,因此拼接之后的数是(A[i]*10[ log10A[j]] + A[j]),它的余数又等于(A[i]*10[ log10A[j] ] %k + A[j] % k)%k。

先看第一项 A[i]*10[ log10A[j] ] %k,由于A[j]的数据范围是1 ≤A[j] ≤109,因此上取整的[ log10A[j] ] 的范围是(1,10),而对k求余,余数肯定小于k的范围,是小于105的,因此我们可以做一张表,用来存放,A[i]不同次方对k求余余数的个数,即brr[幂][A[i]*10[ log10A[j] ] %k]。

再看第二项 A[j] % k,如果上面的表制作成功了,那只需要在表里面找到(k - A[j]%k)这个数,则两项之和一定能被K整除,但是A[j]%k可能为零,因此需要找到的数就是这个(k - A[j]%k)%k。

复杂度分析:
由于需要遍历到每个数需要O(n)的复杂度,求/每一个arr成为拼接后 左边那个数 对k所有余数的个数
需要log10(A[i])的复杂度,总体上是O(nlog10(A[i]))的复杂度。

#include 
#include 
#include 
#include 
using namespace std;
const int nm = 1e5 + 10;
int arr[nm];//存放n个整数
int brr[11][nm];//存放0-10次方 arr%k的余数 的个数
int n,k;
long long cnt = 0;
void work()
{
     
    for(int i = 0;i<n;i++)//遍历每一个数
    {
     
        cnt += brr[(int)log10(arr[i]) + 1][(k - arr[i]%k)%k];
        for(int j = 1,power = 10;j<11;j++)//求出每一个arr成为拼接后 左边那个数 对k所有余数的个数 
        {
     
            brr[j][arr[i]%k*1ll*power%k]++;//a*b%k = ((a%k)*(b%k))%k 防止数据太大
            power = power*10%k;
        }
    }
}
int main()
{
     
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin >> n >> k;
    for(int i = 0;i<n;i++) cin >> arr[i];
    
    work();//A[i]A[j]
    reverse(arr,arr + n);
    memset(brr,0,sizeof brr);
    work();//A[j]A[i]
    
    cout << cnt;
    return 0;
}

注意
1.第一遍work的时候是默认i < j,拼接的顺序是A[i]A[j]组合,因此需要反转一下,从后往前在算一遍。
2.work函数里面,每次计数的时候,是对前一个数不同次方对k求余余数的次数计数,因此不会出现,选到两次相同的数。

你可能感兴趣的:(蓝桥杯,算法)