前几天了解了唯一分解性定理,看了之后感觉定理内容挺简单的,不知道这个定理能干什么,算是比较巧吧,这几天一下遇到了两道需要用唯一分解性定理的题目。
唯一分解定理内容
详细请见百度百科(此处只做简单介绍):百度百科:惟一分解性定理
整数唯一分解定理亦称为算术基本定理,是数论的重要定理之一。该定理断言:任何一个大于1的整数n都可以分解成若干个素因数的乘积,如果不计各个素因数的顺序,那么这种分解是唯一的,即若n > 1,则有:
其中p1 < p2 < … < pk且皆为素数ai(i = 1,2,…,k)皆为正整数。
两道例题
一:阶乘约数
题目描述:
算是一道比较简单的题吧,当时写这一题的时候卡了一下,事后发现题并不难,是一道很模板的题。
思路分析:
将100!分解为若干个素数的乘积,记录每一个素数的种类和个数,以2^3为例有四种选择:指数为0,为1,为2为3,即每个素数因子的选择个数是:其幂次 + 1
直接上代码吧:
#include <iostream>
#include <map>
using namespace std;
map<int,int> m;//键、值分别为素数、素数的个数
void solve(int n)
{
for(int i = 2; i <= n; i++)
{
while(n % i == 0)
{
m[i]++;
n /= i;//分解干净
}
}
}
int main()
{
for(int i = 2; i <= 100; i++)
solve(i);
long long ans = 1;
//因为最终结果的范围不确定且这一题是填空题因此建议直接将类型定义为long long防止爆int
for(auto it = m.begin(); it != m.end(); it++)
{
ans *= (long long)((it -> second) + 1);//每个素数因子的选择个数是:其幂次 + 1
}
cout << ans << endl;
return 0;
}
最终结果为:39001250856960000
这一题比较简单不做过多说明,接下来看第二题.
第二题:反素数
题目描述:
这一题相对上一题来说就比较复杂了,用到了DFS(我会尽快写出DFS和BFS的文章的,最近在准备蓝桥杯有点忙不过来)
题意很简单,但这种题一般都…(血的教训)。孩子,学习算法的水很深,你把握不住,听叔一句劝,不要老想着挣达不溜
开个玩笑,言归正传:首先我们先来简单的分析一下题目
题目分析:
假设存在x为符合题意的最小的反素数,设其约数的个数为cnt,则可能在1~N范围内存在多个约数个数为cnt的数(这一整句话很重要)
这一题需要挖掘的三个性质:
1. 1~N中的符合题意的反素数,就是1~N中约数最多的数中最小的一个.因为如果不是最小的一个,就必然会出现g(x) = g(i)
2. 1~N中任何数的不同的质因子都不会超过10个,因为:2*3*5*7*9*11*13*17*19*23*29*31 > 2 * 10^9
3. x的质因子是连续的若干个最小的质数,且质数的指数是单调递减的.
前两条性质比较简单,下面我们来分析一下第三条性质:
为什么说一定是连续的最小的质数呢(例如2*3*5或2*3*5*7等等),我们不妨来做一个假设:如果我们选择的质数是不连续的,也就是A1 * A3,那么我们还不如选择A1 * A2(这里假设A3和A2指数是相等的),举个例子2^3 * 5^2一定是不如2^3 * 3^2的,二者约数是相等的同时由性质1可知在约数个数相同的情况下当然是约数越小越好。
第二点:为什么说指数一定是递减(或非递增)的呢?如果说c1 < c2,那么c1 + 1,c2 - 1会使得乘积更小,而且约数个数还不变。
综上所述,得到了一个非常简洁的思路:使用DFS,尝试确定前10个质数的指数,然后满足指数单调递减,总乘积不超过N,同时在搜索时记录当前的数的约数的个数。
先来看AC代码吧,一些细节我会在代码中和后面解释的:
//唯一分解性定理:
//#假设存在数x为符合题意的最小反素数,设其约数的个数为cnt,则可能在1~N范围内可能存在多个约数个数为cnt(这句话很重要)
//的数,那么x一定是其中最小的一个
//
//三点说明:1.;2.;3.;
//
//在x之前不可能存在g(x) >= g(i)因为之前我们已经做过了假设
//
//2^3 * 5^2
//2^3 * 3^2
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF = 2e9;
int n;//上限值
LL ans = INF;//最终结果
int cnt_ans = 1;//最终结果的约数的个数
int c[11];//存储每个素数的指数
int p[11] = {0,2,3,5,7,11,13,17,19,23,29};//素数数组
void DFS(int pos,LL num,int cnt)//目前所处的位置,总乘积以及总乘积的约数的个数
{
if(pos == 11)//如果已经遍历过了前10个素数
{
//如果找到了更大的指数和(这意味着会有更多的约数)或指数和相等但是数值本身更小(x是最小反素数)
//注释1
if(cnt > cnt_ans || (cnt == cnt_ans && num < ans))
{
cnt_ans = cnt;//更新约数的个数
ans = num;//更新对应的值
}
return ;
}
for(int i = 0; i <= c[pos - 1]; i++)//指数个数是递减的即本层的素数的指数不能超过上一层的素数的指数
{
if(num > n)//如果数超出了给定的1~N的范围直接结束,因为下一层循环的值一定会使得num更大
return ;
c[pos] = i;//本层选取的素数的指数
DFS(pos + 1,num,cnt * (i + 1));//cnt * (i + 1)为约数的个数(组合)
num *= p[pos];//第i次循环恰好乘了i次
}
}
int main()
{
cin >> n;
c[0] = INF;//因为每个素数的指数是不能超过之前素数的指数的所以把第一个素数的前一个数使用的次数赋一个很大的值
DFS(1,1,1);
cout << ans << endl;
return 0;
}
注释1:控制DFS结束的两个if的条件一般都是不能合并的,这里为不是很熟悉DFS的同学简单说明一下:如果合并的话:
if(pos == 11 && (cnt > cnt_ans || (cnt == cnt_ans && num < ans) ))//如果已经遍历过了前10个素数
{
cnt_ans = cnt;
ans = num;
return ;
}
我们来想象一下递归的过程:每一层递归我们都会为本层递归对应的素数确定指数,我们只需要为10个素数确定指数即可,当递归到达第11层(pos == 11)时,证明我们已经为前10个素数确定了指数,但是!对于这种写法还需要满足:(cnt > cnt_ans || (cnt == cnt_ans && num < ans) )才能进入if语句,才能执行return ;语句,但我们不可能每次到达第11层都会满足上述条件的对吧,如果不满足的话就会继续向下执行,进入下一层递归,继续不满足if条件,继续向下执行,进入下一层递归…也就是可能会找不到递归出口,直至电脑强制结束程序,下面是在我的电脑上的运行结果:
在输入数据后等待了几秒后就结束了程序。
好了,这一片文章到这里就差不多改结束了,如果我由哪些地方写的不合理或者不清楚的话,希望能及时指正。