矩阵翻硬币 蓝桥杯 大数开方 大数相乘

本文介绍了一个有趣的硬币翻转问题,并详细解释了如何通过编程解决该问题,涉及大数乘法、开方及比较等关键算法。
问题描述
  小明先把硬币摆成了一个  n 行 m 列的矩阵

  随后,小明 对每一个硬币分别进行一次 Q 操作

  对第x行第y列的硬币进行 Q 操作的定义: 将所有第 i*x 行,第 j*y 列的硬币进行翻转

  其中i和j为 任意 使操作可行的 正整数 ,行号和列号都是从1开始。
     // i、j为任意数
  当小明对所有硬币都进行了一次 Q 操作后,他发现了一个奇迹——所有硬币均为正面朝上。

  小明想知道最开始有多少枚硬币是反面朝上的。于是,他向他的好朋友小M寻求帮助。

  聪明的小M告诉小明,只需要对所有硬币再进行一次Q操作,即可恢复到最开始的状态。然而小明很懒,不愿意照做。于是小明希望你给出他更好的方法。帮他计算出答案。
输入格式
  输入数据包含一行,两个正整数 n m,含义见题目描述。
输出格式
  输出一个正整数,表示最开始有多少枚硬币是反面朝上的。
样例输入
2 3
样例输出
1
数据规模和约定
  对于10%的数据,n、m <= 10^3;
  对于20%的数据,n、m <= 10^7;
  对于40%的数据,n、m <= 10^15;
  对于10%的数据,n、m <= 10^1000(10的1000次方)。

思路

1. 如果一枚硬币被翻了奇数次,那么它原来的状态肯定是反面朝上,所以,我们要找的就是 被翻了奇数次的硬币(立脚点)
2.  Q 操作的定义:将所有第 i*x 行,第 j*y 列的硬币进行翻转。
正向看可能不好想,那么我们反向看一下,假设一个横坐标为x的硬币,在翻哪些硬币的时候会翻到它呢?
其实就是这个数x所有的约数,比如横坐标为4的硬币,那么,在翻横坐标为1,2,4的硬币时都会翻到它,纵坐标的情况是一样的。
3.对于一个硬币,我们必须同时考虑其横坐标x和纵坐标y,假如横坐标被翻了a次,纵坐标被翻了b次,则这个硬币总共被翻了 a*b次,若想要这个硬币被翻奇数次,a和b必须都得是奇数,即x和y都有奇数个约数
4.那么问题来了:哪些数有奇数个约数呢?不管你知不知道,反正现在你知道了, 完全平方数有奇数个约数。那么什么又是完全平方数呢,简单的说就是n^2,n为自然数,也就是0,2,4,9…… (平方根只有一个数,其他约数都是成对的)
5.问题又来了,怎么求完全平方数的个数呢,首先,我们已经知道了这个矩阵式n*m的,而且是从1开始编号的,对于n,我们可以求sqrt(n),然后取整,容易想出,在1-n的范围内的 完全平方数的个数为(int)(sqrt(n))个,而sqrt(n)*sqrt(m)就是所有的横纵坐标都是完全平方数的硬币的个数。 (完全平方数:1*1、2*2、……,举例:15,位于3*3和4*4之间,开方取整就能够得到3)
6.下面,我们迎来了终极问题,题目思路有了,但是超大规模的数据问题并没有得到解决,反而更麻烦了,因为居然还搞出了个开方的操作,但很不幸,这就是一道悲催的大数高精度题,而且还得自己写出大数相乘,大数开方,大数比较等操作

首先,让我们明确一下思路,到底如何实现乘法和开方,对于大数的存储,我们使用string,因为它的长度比较容易控制
大数运算:
乘法:其实有很多速度快而且更巧妙的大数乘法,但是在蓝桥杯,我们只要使用 模拟手算法应该是够用,虽说是模拟,但也有一些我们常用但不太了解的规律,我们在手算乘法的时候,需要进行 移位和进位,这两个操作我们会通过两步完成
1.移位:对于两个数a=12,b=25,在相乘的时候我们让每一位数分别相乘,即a[0]*b[0]=2 , a[0]*b[1]=5 , a[1]*b[0]=4 , a[1]*b[1]=10,那么规律就是,对于两个数a[i] , b[j],所有 i+j相同的数(i+j相同的数单位相同)都要加到一起,所以我们要把5+4=9合并为一个数,也就是说,将每一位数相乘之后,我们实际得到了2,9,10三个没有进位的数
2.进位:通过上面的操作,我们的2,9,10三个没有进位的数,下面就要进行进位,因为我们是把高位相乘得到的数放在前面,低位相乘得到的数放在后面,所以这三个数排列自然也是高位在前,地位在后,所以要从右向左进位,进位的方法就是a[i+1]=a[i+1]+a[i]/10 , a[i]=a[i]%10,如果放到这三个数上面,就是,9+10/10=10,然后10%10=0,这三个数变成2,10,0,再一次就是2+10/10=3,10%10=0,三个数变为3,0,0,而这正是我们要求的结果,在实际操作中,我们习惯于将数 倒着存放,即将数存为10,9,2,这是 为了进位方便,因为如果正序的话如果最高位发生进位我们就要将每一个数向后移动一位从而挪出一个空位,想想都麻烦,倒序的话因为是向后进位,想怎么进就怎么进。所以刚才的公式改为 a[i]=a[i]+a[i+1]/10(高位=高位+低位/10    低位除10得到进位数) , a[i+1]=a[i+1]%10(低位=低位%10    低位余10得到低位数)

开方:这个开方方法不是我想出来的,是参照了网上的一个方法,我感觉挺好。实际上也可以用牛顿逼近法。
这个方法的前提:假如一个数有n位,若n为 偶数,那么这个数的 方根有n/2位;若 n为奇数,那么方根为 (n+1)/2位
然后,让我们实际看一个例子,我们假设这个数就是1200
1.很明显,它有4位,所以它的方根有2位,然后,我们通过下面的方法来枚举出它的整数根
 00*00=0<1200
    10*10=100<1200
    20*20=400<1200
    30*30=900<1200
      40*40=1600>1200
所以,这个根的十位就是3,然后,再枚举个位
                                                    31*31=961<1200
32*32=1024<1200
33*33=1089<1200
34*34=1156<1200
35*35=1225>1200
所以,这个根就是34,因为 平方增长的速度还是比较快的,所以速度没有太大问题,这里有一个地方需要处理一下,如果一个数很长,那么它的方根的位数也比较长,在枚举高位的时候,我们没有必要把后面的0都加上,因为那样会影响速度,其实0的个数完全可以算出来,然后将0的个数一起送入比较函数比较即可,这样可以提高速度
比较:上面我们说过,比较函数接受的两个数只有一个是完整的数,另外一个实际上是 高几位的平方和0的个数,但处理方式差不多,都是 先比较位数,位数大数就大,位数一样就逐位比较,只要别忘了比较那一堆0就好了,最后其实不用把情况分的太清楚,只要返回0,1就行,因为只有当一个数大于n的时候才对我们有意义

代码

  1. #include<iostream>  
  2. #include<string>  
  3. #include<cstring>  
  4. using namespace std;  
  5.   
  6.   
  7. string strMul(string a,string b)  
  8. {  
  9.     string result="";  
  10.     int len1=a.length();  
  11.     int len2=b.length();  
  12.     int i,j;  
  13.     int num[500]={0};  
  14.     for(i=0;i<len1;i++)  
  15.     for(j=0;j<len2;j++)  
  16.     {  
  17.         num[len1-1+len2-1-i-j]=num[len1-1+len2-1-i-j]+(a[i]-'0')*(b[j]-'0');  
  18.     }  
  19.       
  20.     //for(i=0;i<5;i++)  
  21.     //cout<<num[i]<<' ';  
  22.     //cout<<endl;  
  23.       
  24.     for(i=0;i<len1+len2;i++)  
  25.     {  
  26.         num[i+1]=num[i+1]+num[i]/10;  
  27.         num[i]=num[i]%10;  
  28.     }  
  29.       
  30.     //for(i=0;i<5;i++)  
  31.     //cout<<num[i]<<' ';  
  32.       
  33.     for(i=len1+len2-1;i>=0;i--)  
  34.     {  
  35.         if(num[i]!=0)  
  36.         break;  
  37.     }  
  38.     for(;i>=0;i--)  
  39.     {  
  40.         result=result+(char)(num[i]+'0');  
  41.     }  
  42.     return result;  
  43. }  
  44.   
  45. int strCmp(string a,string b,int pos)  
  46. {  
  47.     int i;  
  48.     //cout<<a<<endl;  
  49.     //cout<<a.length()<<' '<<b.length()<<endl;  
  50.     if(a.length()+pos>b.length())  
  51.     return 1;  
  52.     if(a.length()+pos<b.length())  
  53.     return 0;  
  54.     if(a.length()+pos==b.length())  
  55.     {  
  56.         for(i=0;i<a.length();i++)  
  57.         {  
  58.             if(a[i]<b[i])  
  59.             return 0;  
  60.             if(a[i]==b[i])  
  61.             continue;  
  62.             if(a[i]>b[i])  
  63.             return 1;  
  64.         }  
  65.           
  66.     }  
  67. }  
  68.   
  69. string strSqrt(string a)  
  70. {  
  71.     string result="";  
  72.     int i;  
  73.     int len=a.length();  
  74.     if(len%2==0)  
  75.     len=len/2;  
  76.     else  
  77.     len=len/2+1;  
  78.     for(i=0;i<len;i++)  
  79.     {  
  80.         result=result+'0';  
  81.         while(strCmp(strMul(result,result),a,2*(len-1-i))!=1)  
  82.         {  
  83.             if(result[i]==':')  
  84.             break;  
  85.             result[i]++;  
  86.         }  
  87.         result[i]--;  
  88.     }  
  89.     return result;  
  90. }  
  91.   
  92. int main()  
  93. {  
  94.     string n,m;  
  95.     cin>>n>>m;  
  96.     cout<<strMul(strSqrt(n),strSqrt(m))<<endl;  
  97.     //cout<<strMul("35","35")<<endl;  
  98.     //cout<<strCmp("1024","1200",0);  
  99.     //cout<<strSqrt("9801");  
  100.     return 0;  
  101. }  




























### 光流法C++源代码解析与应用 #### 光流法原理 光流法是一种在计算机视觉领域中用于追踪视频序列中运动物体的方法。它基于亮度不变性假设,即场景中的点在时间上保持相同的灰度值,从而通过分析连续帧之间的像素变化来估计运动方向和速度。在数学上,光流场可以表示为像素位置和时间的一阶导数,即Ex、Ey(空间梯度)和Et(时间梯度),它们共同构成光流方程的基础。 #### C++实现细节 在给定的C++源代码片段中,`calculate`函数负责计算光流场。该函数接收一个图像缓冲区`buf`作为输入,并初始化了几个关键变量:`Ex`、`Ey`和`Et`分别代表沿x轴、y轴和时间轴的像素强度变化;`gray1`和`gray2`用于存储当前帧和前一帧的平均灰度值;`u`则表示计算出的光流矢量大小。 #### 图像处理流程 1. **初始化和预处理**:`memset`函数被用来清零`opticalflow`数组,它将保存计算出的光流数据。同时,`output`数组被填充为白色,这通常用于可视化结果。 2. **灰度计算**:对每一像素点进行处理,计算其灰度值。这里采用的是RGB通道平均值的计算方法,将每个像素的R、G、B值相加后除以3,得到一个近似灰度值。此步骤确保了计算过程的鲁棒性和效率。 3. **光流向量计算**:通过比较当前帧和前一帧的灰度值,计算出每个像素点的Ex、Ey和Et值。这里值得注意的是,光流向量的大小`u`是通过`Et`除以`sqrt(Ex^2 + Ey^2)`得到的,再乘以10进行量化处理,以减少计算复杂度。 4. **结果存储与阈值处理**:计算出的光流值被存储在`opticalflow`数组中。如果`u`的绝对值超过10,则认为该点存在显著运动,因此在`output`数组中将对应位置标记为黑色,形成运动区域的可视化效果。 5. **状态更新**:通过`memcpy`函数将当前帧复制到`prevframe`中,为下一次迭代做准备。 #### 扩展应用:Lukas-Kanade算法 除了上述基础的光流计算外,代码还提到了Lukas-Kanade算法的应用。这是一种更高级的光流计算方法,能够提供更精确的运动估计。在`ImgOpticalFlow`函数中,通过调用`cvCalcOpticalFlowLK`函数实现了这一算法,该函数接受前一帧和当前帧的灰度图,以及窗口大小等参数,返回像素级别的光流场信息。 在实际应用中,光流法常用于目标跟踪、运动检测、视频压缩等领域。通过深入理解和优化光流算法,可以进一步提升视频分析的准确性和实时性能。 光流法及其C++实现是计算机视觉领域的一个重要组成部分,通过对连续帧间像素变化的精细分析,能够有效捕捉和理解动态场景中的运动信息
微信小程序作为腾讯推出的一种轻型应用形式,因其便捷性与高效性,已广泛应用于日常生活中。以下为该平台的主要特性及配套资源说明: 特性方面: 操作便捷,即开即用:用户通过微信内搜索或扫描二维码即可直接使用,无需额外下载安装,减少了对手机存储空间的占用,也简化了使用流程。 多端兼容,统一开发:该平台支持在多种操作系统与设备上运行,开发者无需针对不同平台进行重复适配,可在一个统一的环境中完成开发工作。 功能丰富,接口完善:平台提供了多样化的API接口,便于开发者实现如支付功能、用户身份验证及消息通知等多样化需求。 社交整合,传播高效:小程序深度嵌入微信生态,能有效利用社交关系链,促进用户之间的互动与传播。 开发成本低,周期短:相比传统应用程序,小程序的开发投入更少,开发周期更短,有助于企业快速实现产品上线。 资源内容: “微信小程序-项目源码-原生开发框架-含效果截图示例”这一资料包,提供了完整的项目源码,并基于原生开发方式构建,确保了代码的稳定性与可维护性。内容涵盖项目结构、页面设计、功能模块等关键部分,配有详细说明与注释,便于使用者迅速理解并掌握开发方法。此外,还附有多个实际运行效果的截图,帮助用户直观了解功能实现情况,评估其在实际应用中的表现与价值。该资源适用于前端开发人员、技术爱好者及希望拓展业务的机构,具有较高的参考与使用价值。欢迎查阅,助力小程序开发实践。资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值