自从这个暑假,吴昊学长跟我们讲了数论,然后做了那个多校的数论之后,好像对数论产生了浓厚的兴趣。于是索性接手了数论,从最基础的开始,但无奈最近开始实习,并没有什么时间搞acm……

        从头开始,首先就是lcm与gcd以及各种东西了。

        先是Uva 10780

        大致意思就是,给你两个数字m和n,问m的最多几次方可以整除n的阶乘。以2和10为例,2^8=512可以整除10!而且8不能再大了。前几天刚好看了《具体数学》,上面介绍了一种方法,对于阶乘n!的因子p,它出现的次方为p的倍数、p^2的倍数……之和。同理,我尝试用这种方法去做这道题目。但是发现WA了……看了别人的程序,发现要进行质因数分解,再对分解之后的质因子用如上方式拆分求和。为什么要分解质因数呢?

        这个道理仔细考虑后也是很显然的,因为质因子才是最小分割单元,即不能再被分割。以6和6为例,6!里面只有一个6的倍数,按照之前的做法答案应该是1,但是实际上6=2*3,而6!中有2也有3,因此答案应该为2。即如果一个数字不是质数,那么它可以拆分为另外几个数字,这样有可能不同的因子在不同的数字中取到,也可以构成原来的数字。所以说是先对原数进行质因数分解,然后对这个质数按照之前的方式求出现次数,再看看所有的质因子中,哪一个质因子出现次数最少,最少的即为答案。然后注意如果出现最少的质因子出现次数小于原数中出现次数那么就是Impossible to divide。具体见代码:

#include<bits/stdc++.h>
#define LL long long
using namespace std;

LL n,m;

LL cal(LL n,LL m) //计算每一个质因子出现的次数
{
LL res=0,p=m;
while(p<=n)
{
res+=n/p;
p*=m;
}
return res;
}

int main()
{
int T_T,T;
cin>>T_T;T=T_T;
while(T_T--)
{
scanf("%lld%lld",&m,&n);
printf("Case %d:\n",T-T_T);
LL ans=1e18;
for(int i=2;m>1;i++)
{
LL num=0;
while(m%i==0)num++,m/=i;
if (num)
{
LL res=cal(n,i)/num; //要除以再原数中出现的次数
ans=min(res,ans); //维护出现次数的最小值
}
}
if (ans) printf("%lld\n",ans);
else puts("Impossible to divide");
}
return 0;
}


        接着是Uva 10892,​​传送门~​

        这题意思是说告诉你一个lcm,让你输出以这个lcm为lcm的数对的个数。

        先看一个公式,数字a=p1^a1*p2^a2*p3^a3......,b=p1^b1*p2^b2*p3^b3(其中pi为质因数,ai,bi为幂次),那么他们的LCM c=p1^max(a1,b1)*p2^max(a2,b2)......,同理gcd是取每个质因数幂次最小值。

        然后,对于本题,我们知道了lcm,我们假设lcm=p1^c1*p2^c2*p3^c3.......然后对于每一个pi来说,两个数字,我们只需要有一个数字等于ci就行了,那么当第一个数字为ci的时候,第二个数字就可以有0……ci-1个取值对应ci种方案,然后第二个数字为ci时也有这么多种方案,最后还有两个数字都为ci的情况,总共对于质因子pi来说,就有ci*2+1种贡献。把所有的pi产生的贡献用乘法原理乘起来。最后结果要注意,除了(lcm,lcm)这组数以外,所有的数对都被算了两次,前后位置的缘故,所以最后结果要除以二再加一。

        另外,此题的数据较大,不可能把1e9级别的质数都求出来,所以我们用欧拉筛筛素数的时候只需要筛到sqrt(n)即可。而由于这个,到了最后我们可能剩下一个很大的素数没有整除完,所以如果发生这种情况,我们要再算一次。具体见代码:

#include<bits/stdc++.h>
#define N 500100
using namespace std;

int prime[N],m,n,num;
bool v[N];

void init() //欧拉筛线性筛素数
{
for(int i=2;i<=50000;i++)
{
if (!v[i]) prime[++m]=i;
for(int j=1;j<=m;j++)
{
if (i*prime[j]>50000) break;
v[i*prime[j]]=1;
if (i%prime[j]==0) break;
}
}
}

int main()
{
init();
while(~scanf("%d",&n)&&n)
{
int ans=1; num=n;
for(int i=1;i<=m&&n>0;i++)
{
if (n%prime[i]) continue; int p=0;
while(n%prime[i]==0) p++,n/=prime[i]; //对原数惊醒质因数分解,求出pi对应的ci
if (p!=0) ans*=(p<<1)+1; //计算每个pi的贡献,乘以二加一
}
if (n>1) ans+=ans<<1; ans=(ans>>1)+1; //如果最后剩下一个大素数,那么多计算一次。最后结果除以二加一
printf("%d %d\n",num,ans);
}
return 0;
}


        最后是Uva 11076​

        这题的话就是给出你一组数字,问你把这一组数字的所有排列加起来的和是多少。经过一段时间的多校训练赛的洗礼,我自己已经深刻的掌握了求一些方案书以及和的方法。而这题就是典型的计算贡献的方法。由于给出的数字是确定的,所以我们可以考虑每个数字对最后结果的贡献。经过推理,我们可以发现,每一个数字出现在每一位的次数是相等的。例如<1,1,2>,它的排列有<1,1,2><1,2,1><2,1,1>,考虑把两个1看作不同的1,那么每一个数字都在每一位出现了相同次数。假设总共有三个数字,那么对于某一个数字i,它在每一位出现对最后的贡献就是i*111*排列数/3。这样就很清晰明了了,相当于最后结果ans=sum*111*排列数/3,其中排列数=n!/(a[i]!),a[i]表示数字i出现的次数,sum表示所有的i之和。这个结论应该是比较好理解的。具体见代码:

#include<bits/stdc++.h>
using namespace std;

int a[10],n,one,num;

unsigned long long fac[15];

void init() //初始化阶乘
{
fac[0]=1;
for(int i=1;i<=12;i++)
fac[i]=fac[i-1]*i;
}

int main()
{
init();
while(~scanf("%d",&n)&&n)
{
memset(a,0,sizeof(a));
unsigned long long one=0;
unsigned long long sum=0;
for(int i=1;i<=n;i++)
{
int x; scanf("%d",&x);
sum+=x; one=one*10+1; a[x]++;
}
sum*=fac[n-1]; //*n!/n==*(n-1)!
for(int i=0;i<=9;i++)
sum/=fac[a[i]]; // /(a[i]!)
cout<<sum*one<<endl;
}
return 0;
}