随后,小明对每一个硬币分别进行一次 Q 操作。
对第x行第y列的硬币进行 Q 操作的定义:将所有第 i*x 行,第 j*y 列的硬币进行翻转。
其中i和j为任意使操作可行的正整数,行号和列号都是从1开始。
当小明对所有硬币都进行了一次 Q 操作后,他发现了一个奇迹——所有硬币均为正面朝上。
小明想知道最开始有多少枚硬币是反面朝上的。于是,他向他的好朋友小M寻求帮助。
聪明的小M告诉小明,只需要对所有硬币再进行一次Q操作,即可恢复到最开始的状态。然而小明很懒,不愿意照做。于是小明希望你给出他更好的方法。帮他计算出答案。
对于20%的数据,n、m <= 10^7;
对于40%的数据,n、m <= 10^15;
对于10%的数据,n、m <= 10^1000(10的1000次方)。
1.很容易得出,如果一枚硬币被翻了奇数次,那么它原来的状态肯定是反面朝上,所以,我们要找的就是被翻了奇数次的硬币
2. 根据Q操作定义,我们举个例子,对于(2,3)这个点只有在(1,1)(1,3)(2,1)(2,3)这四个点进行Q操作时才翻转,一共翻转了4次,通过多个例子总结不难看出,(x,y)点再所有点进行完Q操作后翻转的次数为a*b次,其中为x的约数,b为y的约数。因此若想要这个硬币被翻奇数次,a和b必须都得是奇数,即x和y都有奇数个约数。
想到这如果数据量不大就想到了如下算法
#include <iostream>
#include <algorithm>
using namespace std;
long long numy(long long n)
{
if(n==1)
return 1;
long num=0;
for(long long i=2;i<=n/2;i++)
if(n%i==0)
num++;
return num+2;
}
int main()
{
long long n,m,num=0;
cin>>n>>m;
for(long long i=1;i<=n;i++)
for(long long j=1;j<=m;j++)
if((numy(i)*numy(j))%2==1)
num++;
cout<<num<<endl;
return 0;
}
在这个题中这个算法明显是超时的必须另寻他法
先普及一个数论知识:
完全平方数有奇数个约数。那么什么是完全平方数呢,简单的说就是n^2,n为自然数,也就是0,2,4,9……
那个这个问题是不是就转化成了
输入两个数n,m,设小于等于n的完全平方数的个数是a,小于等于m的完全平方数的个数是b,求a*b。
那么怎么求小于等于n和m完全平方数的个数呢?
再普及一个知识:
小于等于n的完全平方数的个数为[sqrt(n)]个,即为n的平方根向下取整
那么这个题目有转化成了
输入两个数n,m,求[sqrt(n)]*[sqrt(m)]。
这样题目就简单很多了,只要解决两个问题就可以
1.大数的乘法
2.大数的平方根取整
问题1解决方法:
先弄两个代表数值的字符串s1,s2,将s1的每一位与s2相乘,存到一个整形数组里
举个例子s1=“123”,s2=“89”
123*89
S数组操作 |
将s计算结果存到num数组的位置 |
num数组中的计算 |
num数组内数的情况 |
s1[0]*s2[0] |
num[1] |
0+1*8=8 |
0 8 0 0 0 |
s1[0]*s2[1] |
num[2] |
0+1*9=9 |
0 8 9 0 0 |
s1[1]*s2[0] |
num[2] |
9+2*8=25 |
0 8 25 0 0 |
s1[1]*s2[1] |
num[3] |
0+2*9=18 |
0 8 25 18 0 |
s1[2]*s2[0] |
num[3] |
18+3*8=42 |
0 8 25 42 0 |
s1[2]*s2[1] |
num[4] |
0+3*9=27 |
0 8 25 42 27 |
存完后,对num数组进行倒着计算
比如27实际上就是123*89计算中3*9=27,即把7留下2进上去
Num数组计算 |
0 8 25 44 7 |
0 8 29 4 7 |
0 10 9 4 7 |
1 0 9 4 7 |
问题2解决方法:
这个开方方法不是我想出来的,是参照了大神的一个方法
假如一个数有偶数位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
所以,这个根就是34,因为平方增长的速度还是比较快的,所以速度没有太大问题。为了提高速度,我们可以优化比较函数:
还拿上面的例子来说
30*30=900<1200
40*40=1600>1200
于是我们可以计算需要加的0的数量然后用下面的方法直接比较
1.如果第i个数的平方的位数加上需要添加个零之后位数与原数不相等,那么位数大的数值大
2.如果位数相等就没必要再添零,直接进行字符串比较即可
例如:
30*30=900<1200
40*40=1600>1200
十位是3的时候 3*3=9是1位填上两个零后位数位3位小于1200的4位所以900<1200
十位是4的时候 4*4=16是两位,添上两个零后位数为4位等于1200的四位,所以只需比较字符串16与1200的大小
很明显在字符串中16>1200,所以1600>1200
那么添加零的个数怎么算呢?
假设一个数的平方根取整的位数为n,从前往后算目前计算到了第i位,则需要添加2*(n-1-i)个零
例如:1200 平方根取整有2位,目前算到了第0位(从0开始计数)即30*30(我们算的是3*3),需要加2*(2-1-0)=2个零
#include <iostream>
#include <algorithm>
using namespace std;
string StrMul(string s1,string s2)//大数乘法
{
string ans;
int num[500]={0},i,j;
for(i=0;i<s1.length();i++)//s计算存到num中
for(j=0;j<s2.length();j++)
num[i+j+1]+=(s1[i]-'0')*(s2[j]-'0');
for(i=s1.length()+s2.length()-1;i>0;i--)//num的处理
if(num[i]>=10)
{
num[i-1]+=num[i]/10;
num[i]%=10;
}
for(int i=0;i<=s1.length()+s2.length()-1;i++)//将num数存到ans字串中,注意进位为0的情况
if(!i&&num[i]||i)
ans.push_back(num[i]+'0');
return ans;
}
bool StrCmp(string s1,string s2,int pos)//比较两字符串大小,pos代表应该在s1后面填几个零
{
if(s1.length()+pos!=s2.length())//如果s1位数不等于s2,
return s1.length()+pos>s2.length();
else//位数相等
return s1>s2;
}
string SqrtStr(string s)//大数平方根取整
{
int len;
string ans;
if(s.length()%2==0)//长度为偶数
len=s.length()/2;
else
len=s.length()/2+1;
for(int i=0;i<len;i++)//一位一位的循环
{
ans.push_back('0');
for(int j=0;j<=9;j++)
{
if(StrCmp(StrMul(ans,ans),s,2*(len-1-i)))//需要添加0的个数是2*(len-1-i)解析见上面
break;
ans[i]++;
}
ans[i]--;
}
return ans;
}
int main()
{
string s1,s2;
cin>>s1>>s2;
cout<<StrMul(SqrtStr(s1),SqrtStr(s2))<<endl;
return 0;
}