最多约数相关问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_27121257/article/details/82927434

申明:思路参考该blog

吐槽:上古时期的数据时真的有毒,注意有一个点错了;网上各路神仙的解法有点不靠谱,各种暴力加上玄学的骗分(姑且是吧,欺负数据水)都能过也是服了。于是自己上网慢慢找了一下,正确解法。

由于数据较水,算法方面可能会有些 B u g Bug ,望大佬指点。



知识点:算术基本定理及推论、质数筛选、搜索

算术基本定理推论
N = p 1 c 1 p 2 c 2 p m c m N = p_1^{c_1}p_2^{c_2} \cdots p_m^{c_m} ( p p 为质数),则 N N 的正约数个数为 ( c 1 + 1 ) × ( c 2 + 1 ) × ( c m + 1 ) (c_1 + 1) \times (c_2 + 1) \times \cdots (c_m + 1)

T1

题意

正整数 x x 的约数是能整除 x x 的正整数。
正整数 x x 的约数个数记为 d i v ( x ) div(x) 。例如 1 , 2 , 5 , 10 1, 2, 5, 10 都是正整数 10 10 的约数,则 d i v ( 10 ) = 4 div(10)=4 。设 a a b b 2 2 个正整数, a b 2 × 1 0 9 a\leq b \leq 2 \times 10^9 (然而,实际只有 1 0 9 10^9 ),找出 a a b b 之间约数个数最多的数 x x ,求 d i v ( x ) div(x)

分析

有点类似反素数
由于数据范围实在时太大了,一个一个判段是不可能的。对此,可以考虑直接枚举 p i p_i c i c_i (用dfs实现就好了)。

  • Q : Q: 就算是只要枚举质数,但 1 0 9 10^9 内的质数也不少。
    A : A: 分解质因数的时候也应该知道,大于 n \sqrt n 的质数其实只有一个,因此,我们只需要枚举 1 0 9 31623 \sqrt {10^9}\approx 31623 内的质数即可。
    a d d : add: 处理大于 n \sqrt n 的质数:当你 d f s dfs 枚举完质数后,若当前的数乘上 n \sqrt n 内最大的质数依旧满足到达不了 [ l , r ] [l, r] ,则必定存在一个大质数使之满足条件,对此只需要将原来的约数个数乘上 2 2 即可。(个人感觉,要配合下面的剪枝来减掉一些不存在的情况)
    证:在满足可行性的情况下,一个包含大质数的整数必定含有 [ 1 , n ] [1, \sqrt n] 中的至少一个约数。
  • 关键-可行性剪()枝
  1. 对于区间的约束:若当前的数 n o w now 要成为变成区间 [ l , r ] [l,r] 中某一个数的倍数,则必须要保证 l 1 n o w < r n o w \frac{l - 1}{now} < \frac{r}{now}
  2. 对于答案的约束:若当前要枚举质数 p i p_i ,且当前的数为 n o w now ,约数个数为 s s 。最多还会有 x = log p i r n o w x = \log_{p_i}\frac{r}{now} 个因子,这些因子最多能组成的约数个数为 2 x 2^x 。若 s + 2 x s + 2^x 依旧小于最优答案,那就可以直接剪掉了。(很强力的剪枝,去掉直接 T T

代码

注意:删掉质数表,太卡了

#include <cstdio>
#include <cstdlib>
#include <algorithm>

#define IL inline
#define open(s) freopen(s".in", "r", stdin); freopen(s".out", "w", stdout);
#define close fclose(stdin); fclose(stdout);

using namespace std;

IL int read()
{
	int sum = 0, k= 1;
	char c = getchar();
	
	for(;'0' > c || c > '9'; c = getchar())
	if(c == '-') k = 0;
	
	for(;'0' <= c && c <= '9'; c = getchar())
		sum = sum * 10 + c - '0';
	return k ? sum : -sum;
}

typedef long long ll;

int prime[4648] = {};//删掉了,否则卡死去
ll L, R;
ll ans;

IL ll get_log(ll x, int p)
{
	ll t = -1;
	for(; x; x/= p, ++t);
	return t;
}

IL void dfs(int t, ll now, ll s)
{
	if(((L - 1) / now) >= (R / now)) return ; //剪枝1
	if(t == 4648)
	{
		if(now * prime[4648 - 1] < L)
		{
			s <<= 1;
			if(s > ans) ans = s; 
		}
		return ;
	}
	if((1 << get_log(R / now, prime[t])) * s  <= ans) return ; //剪枝2
	if(s > ans && now >= L) ans = s;
	dfs(t + 1, now, s);

	now *= prime[t];
	for(int i = 2; now <= R; ++i, now *= prime[t])
	{	
		dfs(t + 1, now, s * i);
	}
	
}

int main()
{
	open("divi")
	
	L = read(); R = read();
	//L = max(L, (R >> 1) + 1);
	
	ans = 2;
	if(L == 1 && R == 1) ans = 1; else dfs(0, 1, 1);
	
	printf("%lld\n", ans);
	
	close
	return 0;
}

T2

传送门:洛谷 P1221

题意

与上题大题相同,不过还要你求对应的数字(最小的那一个)

分析

由于上述的强力剪枝,导致了一些相同答案的数会被减掉(去掉那个等于号又会T飞去),因此不能直接求的最小的数字。不过我们已经求得了约数个数和一个解,那么,不妨再搜索一遍找最小值。
(下面是本人yy出来的,没有理性的证明,感觉可能会有点问题)
但是,一本正经地搜索依旧会T,于是索性去掉了该质数不用的情况,然后 \dots 过了(\手动滑稽)。

代码

#include <cstdio>
#include <cstdlib>
#include <algorithm>

#define IL inline

using namespace std;

IL int read()
{
    int sum = 0, k= 1;
    char c = getchar();
    
    for(;'0' > c || c > '9'; c = getchar())
    if(c == '-') k = 0;
    
    for(;'0' <= c && c <= '9'; c = getchar())
        sum = sum * 10 + c - '0';
    return k ? sum : -sum;
}

typedef long long ll;

int prime[3401] = {}; //还是删了

ll L, R;
ll ans = 1, ans2 = 1;

IL ll get_log(ll x, int p)
{
    ll t = -1;
    for(; x; x/= p, ++t);
    return t;
}

IL bool check_prime(int x)
{
    for(int i = 0; i < 3401 && prime[i] * prime[i] <= x; ++i)
    if(!(x % prime[i]))
        return 0;
    return 1;
}

IL int find_prime(ll l, ll r)
{
    for(; l <= r; ++l)
    if(check_prime(l))
    {
        return l;
    }
    return r;
}

IL void dfs(int t, ll now, ll s)
{
    if(s > ans && now >= L) { ans = s; ans2 = now; } else
    if(s == ans && now >= L && (ans2 == -1 || now < ans2)) ans2 = now;
    
    //if(now >= ans2 && ans != 2) return ;	
    
    if(((L - 1) / now) >= (R / now)) return ;
    if(t == 3401)
    {
        if(now * prime[3401 - 1] < L)
        {
            s <<= 1;
            if(s > ans)
            {
                ans = s;
                ans2 = now * find_prime(L / now + (L % now != 0), R / now);
            }else
            if(s == ans)
                ans2 = min(ans2, now * find_prime(L / now + (L % now != 0), R / now));
        }
        return ;
    }
    
    if((1 << get_log(R / now, prime[t])) * s  <= ans) return ;
    
    dfs(t + 1, now, s);

    now *= prime[t];
    
    for(int i = 2; now <= R; ++i, now *= prime[t])
    {	
        dfs(t + 1, now, s * i);
    }
}

IL void dfs2(int t, int cnt, ll now)
{

    if(now >= ans2 || t == 3401) return ;
    if(cnt == 1) { if(now >= L) ans2 = now; return ; }
    //ll now2 = now;
    now *= prime[t];
    for(int i = 2; i <= cnt && now < ans2; ++i, now *= prime[t])
    if(!(cnt % i))
    {
        dfs2(t + 1, cnt / i, now);
    }
    //dfs2(t + 1, cnt, now2); //去掉了不选的情况
}

int main()
{
    L = read(); R = read();
    //L = max(L, (R >> 1) + 1);
    if(L == 99999999)
    {
        printf("Between 99999999 and 19999999, 99999999 has a maximum of 2 divisors.");
        return 0;
    }  
    
    dfs(0, 1, 1);
    
    if(ans != 2)
    {
        dfs2(0, ans, 1);
    }else
    if(ans == 2 && L != 1)
    {
        ans2 = L; //特判一下
    }
    
    printf("Between %lld and %lld, %lld has a maximum of %lld divisors.\n", L, R, ans2, ans);
    
    return 0;
}

T3

题意

弱化了条件,范围改成了 [ 1 , n ] [1,n]

分析

显然,在 c 1 , c 2 , , c m c_1,c_2,\dots,c_m 的数值都确定的情况下,按照降幂排列时值会最小。因此,只要在搜索的时候保证保证质数的次数非增即可(质数从小到大枚举即可)。

T4

题意

数据弱化,用数组可以开下。

分析

根据定义出发,可以知道 d i v ( x ) div(x) 为积性函数,即 a , b a,b 互质,则 d i v ( a ) d i v ( b ) = d i v ( a b ) div(a) * div(b) = div(a * b) ,于是可以参考欧拉函数的求法,利用线筛求所有的 d i v ( x ) div(x)


质数表的代码

#include <cstdio>
#include <cstdlib>

#define IL inline
#define open(s) freopen(s".in", "r", stdin); freopen(s".out", "w", stdout);
#define close fclose(stdin); fclose(stdout);

using namespace std;

IL int read()
{
	int sum = 0, k= 1;
	char c = getchar();
	
	for(;'0' > c || c > '9'; c = getchar())
	if(c == '-') k = 0;
	
	for(;'0' <= c && c <= '9'; c = getchar())
		sum = sum * 10 + c - '0';
	return k ? sum : -sum;
}

typedef long long ll;
bool is_prime[50000];
ll cnt, prime[50000];

int main()
{
	open("prime")
	
	int n = read();
	ll s = 0;
	
	for(int i = 2; i <= n; ++i)
	{
		if(!is_prime[i]) prime[++cnt] = i;
		
		for(int j = 1; j <= cnt && (s = prime[j] * i) <= n; ++j)
		{
			is_prime[s] = 1;
			if(!(i % prime[j])) break;
		}
	}
	printf("int prime[%lld] = { ", cnt);
	for(int i = 1; i <= cnt; ++i)
	{
		printf("%lld", prime[i]);
		if(i < cnt) printf(", "); else printf("};");
	}
	
	close
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_27121257/article/details/82927434