申明:思路参考该blog
吐槽:上古时期的数据时真的有毒,注意有一个点错了;网上各路神仙的解法有点不靠谱,各种暴力加上玄学的骗分(姑且是吧,欺负数据水)都能过也是服了。于是自己上网慢慢找了一下,正确解法。
由于数据较水,算法方面可能会有些 ,望大佬指点。
知识点:算术基本定理及推论、质数筛选、搜索
算术基本定理推论:
若
(
为质数),则
的正约数个数为
T1
题意
正整数
的约数是能整除
的正整数。
正整数
的约数个数记为
。例如
都是正整数
的约数,则
。设
和
是
个正整数,
(然而,实际只有
),找出
和
之间约数个数最多的数
,求
。
分析
有点类似反素数
由于数据范围实在时太大了,一个一个判段是不可能的。对此,可以考虑直接枚举
与
(用dfs实现就好了)。
-
就算是只要枚举质数,但
内的质数也不少。
分解质因数的时候也应该知道,大于 的质数其实只有一个,因此,我们只需要枚举 内的质数即可。
处理大于 的质数:当你 枚举完质数后,若当前的数乘上 内最大的质数依旧满足到达不了 ,则必定存在一个大质数使之满足条件,对此只需要将原来的约数个数乘上 即可。(个人感觉,要配合下面的剪枝来减掉一些不存在的情况)
证:在满足可行性的情况下,一个包含大质数的整数必定含有 中的至少一个约数。 - 关键-可行性剪(
纸)枝
- 对于区间的约束:若当前的数 要成为变成区间 中某一个数的倍数,则必须要保证
- 对于答案的约束:若当前要枚举质数 ,且当前的数为 ,约数个数为 。最多还会有 个因子,这些因子最多能组成的约数个数为 。若 依旧小于最优答案,那就可以直接剪掉了。(很强力的剪枝,去掉直接 )
代码
注意:删掉质数表,太卡了
#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,于是索性去掉了该质数不用的情况,然后
过了(\手动滑稽)。
代码
#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
题意
弱化了条件,范围改成了
分析
显然,在 的数值都确定的情况下,按照降幂排列时值会最小。因此,只要在搜索的时候保证保证质数的次数非增即可(质数从小到大枚举即可)。
T4
题意
数据弱化,用数组可以开下。
分析
根据定义出发,可以知道 为积性函数,即 互质,则 ,于是可以参考欧拉函数的求法,利用线筛求所有的
质数表的代码
#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;
}