@description@

若一个大于 1 的整数 M 的质因数分解有 k 项,其最大的质因子为 \(A_k\),并且满足 \(A_k^k \le N\),\(A_k < 128\),我们就称整数 M 为 N - 伪光滑数。

现在给出 N,求所有整数中第 K 大的 N - 伪光滑数。

input

只有一行,为用空格隔开的整数 N 和 K 。2 ≤ N ≤ 10^18, 1 ≤ K ≤ 800000,保证至少有 K 个满足要求的数

output

只有一行,为一个整数,表示答案。

sample input

12345 20

sample output

9167

@solution@

首先,如果对于质数 x 以及一个整数 k,满足 \(x^k \le N\)。则只要保证剩下的 k-1 个质因数小于等于 x 就可以构成伪光滑数了。

所以假如我们确定了 x 和 k,题目中给出的关于伪光滑数的约束统统没有用。

@version - 1@

关于求解第 k 大,这里显然二分答案是不可做的(upd in 2019/2/12:还真的被我找到了二分答案的做法……不过我并不打算写因为我太懒了)。我们可以通过类比 k 短路的做法来做。简略地说一下:

当给定 x 和 k 时,显然最大的数是 k 个 x 相乘,我们把这个当作初始状态。考虑设计一个不重复不遗漏的初始状态与某一状态之间的转移,且要使得转移始终是递减的。

假如某状态 \(m = p_1^{a_1}*p_2^{a_2}*\dots*p_r^{a_r}\),其中 \(p_1<p_2<\dots<p_r=x\)。可以把这个状态看作 \([1, a_1+a_2+\dots+a_r]\) 这些位置填 \(p_r\),再 \([1, a_1+a_2+\dots+a_{r-1}]\) 这些位置填 \(p_{r-1}\) 并覆盖掉之前填的质因子,然后重复操作……

于是,我们这样来转移:状态里面存储当前的数是什么,最小质因子和次小质因子是什么,最小质因子和次小质因子的出现次数。每次两类转移,次小换最小或者新增更小。前者要保证次小的质因子次数为正。

upd in 2019/2/12:

时间复杂度应该是 O(nlog^2n) 的级别。不过我们要相信玄学,一个数的质因子怎么可能卡得满这个上界。

反正能过啦,管那么多干嘛。

实际上,有一些代码实现可以做到 O(nlog n) 的复杂度,不过我看不懂……

@version - 2@

对于这道题,我们有一个更暴力(应该是吧?毕竟内存消耗更多些)的方法。

我们一样给定 x 和 k。然后对于每一对 (x, k),我们把所有的可能的答案存储在堆内。

当然不可能是直接存,我们使用可持久化左偏树来搞。

定义 \(f(x, k)\) 表示最大的质因子为 x,质因子个数为 k 时的左偏树。

讨论次大的数是什么,可以得到 \(f(x, k)\) 是由所有满足 i <= x 且 i 为质数的左偏树 \(f(i, k-1)\) 全部合并起来再给所有元素乘 x 得到。

后一个操作可以用打 tag 的方式实现。

但是这样空间开销还是很大。我们考虑给左偏树求前缀和。

定义 \(g(x, k)\) 表示所有满足 i <= x 且 i 为质数的左偏树 \(f(i, k)\) 全部合并起来得到的左偏树。

就有 \(g(x, k)\) 等于 \(f(x, k)\) 与 \(g(x-1, k)\)的合并,\(f(x, k)\) 等于 \(g(x, k-1)\) 这棵树整体乘 x。

这样每增加一个状态最多只会多合并一次。

对,f 和 g 都是一个左偏树。

对,你可以理解为左偏树用来作 dp。

对,很神奇,我也没见过。

下传标记的时候记得也要新建结点。

@accepted code@

因为我实在是太懒了,仅给出第二种方法的代码。

upd in 2019/2/12:已经更新了第一种方法的代码啦~

@version - 1@

#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXK = 800000;
struct node{
int a1, a2, b1, b2; ll c;
node(int _a1=0, int _a2=0, int _b1=0, int _b2=0, ll _c=0):a1(_a1), a2(_a2), b1(_b1), b2(_b2), c(_c){}
};
bool operator < (node a, node b) {
return a.c < b.c;
}
bool is_prm(int n) {
for(int i=2;i<n;i++)
if( n % i == 0 ) return false;
return true;
}
int prm[128], pcnt;
priority_queue<node>que;
int main() {
ll N; int K;
scanf("%lld%d", &N, &K);
for(int i=2;i<128;i++) {
if( !is_prm(i) ) continue;
prm[++pcnt] = i;
for(ll nw=1,cnt=1;nw<=N/i;nw*=i,cnt++)
que.push(node(0, pcnt, 0, cnt, nw*i));
}
ll ans;
for(int i=1;i<=K;i++) {
node t = que.top(); que.pop();
if( t.b1 > 1 )
que.push(node(t.a1, t.a2, t.b1 - 1, t.b2 + 1, t.c/prm[t.a1]*prm[t.a2]));
if( t.b2 > 1 ) {
for(int i=1;i<t.a2;i++)
que.push(node(t.a2, i, t.b2 - 1, 1, t.c/prm[t.a2]*prm[i]));
}
ans = t.c;
}
printf("%lld\n", ans);
}

@version - 2@

#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXK = 800000;
struct node{
node *ch[2];
ll key, tag; int dis;
}pl[16000000 + 5], *rt1[128][64], *rt2[128][64], *tcnt, *NIL;
void pushdown(node *x) {
if( x->tag != 1 ) {
if( x->ch[0] != NIL ) {
tcnt++; (*tcnt) = *(x->ch[0]);
tcnt->tag *= x->tag, tcnt->key *= x->tag;
x->ch[0] = tcnt;
}
if( x->ch[1] != NIL ) {
tcnt++; (*tcnt) = *(x->ch[1]);
tcnt->tag *= x->tag, tcnt->key *= x->tag;
x->ch[1] = tcnt;
}
x->tag = 1;
}
}
struct node2{
node *x;
node2(node *_x):x(_x){}
};
bool operator < (node2 a, node2 b) {
return a.x->key < b.x->key;
}
node *newnode(ll x) {
tcnt++;
tcnt->ch[0] = tcnt->ch[1] = NIL;
tcnt->key = x, tcnt->tag = 1, tcnt->dis = 1;
return tcnt;
}
node *merge(node *x, node *y) {
if( y == NIL ) return x;
if( x == NIL ) return y;
node *p = (++tcnt);
if( x->key < y->key ) swap(x, y);
(*p) = (*x); pushdown(p);
p->ch[1] = merge(p->ch[1], y);
if( p->ch[0]->dis < p->ch[1]->dis ) swap(p->ch[0], p->ch[1]);
p->dis = p->ch[1]->dis + 1;
return p;
}
void init() {
NIL = tcnt = &pl[0];
NIL->dis = 0;
for(int i=0;i<64;i++)
rt2[0][i] = NIL;
for(int i=0;i<128;i++)
rt2[i][0] = NIL;
}
bool is_prm(int n) {
for(int i=2;i<n;i++)
if( n % i == 0 ) return false;
return true;
}
int prm[128], mxk[128], pcnt;
priority_queue<node2>que;
int main() {
ll N; int K; init();
scanf("%lld%d", &N, &K);
for(int i=2;i<128;i++) {
if( !is_prm(i) ) continue;
prm[++pcnt] = i;
for(ll nw=1;nw<=N/i;nw*=i) mxk[pcnt]++;
}
for(int i=1;i<=pcnt;i++)
if( mxk[i] ) {
rt1[i][1] = newnode(prm[i]), rt2[i][1] = merge(rt2[i-1][1], rt1[i][1]);
for(int j=2;j<=mxk[i];j++) {
rt1[i][j] = (++tcnt); (*rt1[i][j]) = (*rt2[i][j-1]);
rt1[i][j]->key *= prm[i], rt1[i][j]->tag *= prm[i];
rt2[i][j] = merge(rt2[i-1][j], rt1[i][j]);
}
/*for(node *i=&pl[1];i<=tcnt;i++)
printf("%d %d %d %lld %lld\n", i-pl, i->ch[0]-pl, i->ch[1]-pl, i->key, i->tag);*/
}
for(int i=1;i<=pcnt;i++)
for(int j=1;j<=mxk[i];j++)
que.push(rt1[i][j]);
ll ans;
for(int i=1;i<=K;i++) {
node2 f = que.top(); que.pop();
pushdown(f.x);
que.push(node2(merge(f.x->ch[0], f.x->ch[1])));
ans = f.x->key;
}
printf("%lld\n", ans);
}

@details@

一开始我在合并的时候,某一个结点等于 NIL 的时候新建了一个结点来存另一个结点。

但是极限数据始终不是 RE 就是 MLE。

后来我选择直接返回另一个结点,然后就没问题了。

连这个空间也要卡吗……

@bzoj - 4524@ [Cqoi2016]伪光滑数的更多相关文章

  1. Bzoj 4524 [Cqoi2016]伪光滑数(堆)

    题面 题解 先筛出$<128$的质数,很少,打个表即可 然后钦定一个质数最大,不断替换即可(丢进大根堆里面,然后取出一个,替换在丢进去即可) 具体来说,设一个四元组$[t,x,y,z]$表示当前 ...

  2. 【BZOJ4524】[Cqoi2016]伪光滑数 堆(模拟搜索)

    [BZOJ4524][Cqoi2016]伪光滑数 Description 若一个大于1的整数M的质因数分解有k项,其最大的质因子为Ak,并且满足Ak^K<=N,Ak<128,我们就称整数M ...

  3. [CQOI2016]伪光滑数

    题目描述 若一个大于1的整数M的质因数分解有k项,其最大的质因子为Ak,并且满足Ak^K<=N,Ak<128,我们就称整数M为N-伪 光滑数.现在给出N,求所有整数中,第K大的N-伪光滑数 ...

  4. [bzoj4524] [loj#2047] [Cqoi2016] 伪光滑数

    Description 若一个大于 \(1\) 的整数 \(M\) 的质因数分解有 \(k\) 项,其最大的质因子为 \(Ak\) ,并且满足 \(Ak^K \leq N\) , \(Ak<12 ...

  5. BZOJ4524 [Cqoi2016]伪光滑数

    BZOJ上的题面很乱,这里有一个题面. 题解: 正解是可持久化可并堆+DP,可惜我不会... 但暴力也可过这道题. 先在不超过N的前提下,在大根堆里加入每个质数的J次方,1<=j, 然后就可以发 ...

  6. BZOJ4524 CQOI2016伪光滑数(堆)

    对于每个质数求出其作为最大质因子时最多能有几个质因子,开始时将这些ak1~akmaxk扔进堆.考虑构造方案,使得每次取出最大值后,最大质因子.质因子数均与其相同且恰好比它小的数都在堆里.类似暴搜,对于 ...

  7. 【BZOJ-4524】伪光滑数 堆 + 贪心 (暴力) [可持久化可并堆 + DP]

    4524: [Cqoi2016]伪光滑数 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 183  Solved: 82[Submit][Status] ...

  8. 2021.08.01 P4359 伪光滑数(二叉堆)

    2021.08.01 P4359 伪光滑数(二叉堆) [P4359 CQOI2016]伪光滑数 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题意: 若一个大于 11 的整数 MM ...

  9. Loj 2047 伪光滑数

    Loj 2047 伪光滑数 正解较复杂,但这道题其实可以通过暴力解决. 预处理出 \(128\) 内的所有质数,把 \(n\) 内的 \(prime[i]^j\) 丢进堆中,再尝试对每个数变形,除一个 ...

随机推荐

  1. Scanner读取记事本文件内容为空的解决办法

    原因:记事本txt文件中含有中文,windows记事本编码方式为gbk,但是eclipse中为utf-8,所以需要在Scanner中指定编码方式.

  2. Redis分布式锁的实现及注意事项

    一.前言 分布式锁一般有三种实现方式: 1. 数据库乐观锁: 2. 基于Redis的分布式锁: 3. 基于ZooKeeper的分布式锁. 本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上 ...

  3. Django项目:CRM(客户关系管理系统)--04--02PerfectCRM创建ADMIN页面02

    十.CRM项目创建模板页面 {#king_base.html#} {## ————————02PerfectCRM创建ADMIN页面————————#} {#模板文件 king_base.html#} ...

  4. 洛谷 2055 [ZJOI2009]假期的宿舍——二分图匹配

    题目:https://www.luogu.org/problemnew/show/P2055 #include<iostream> #include<cstdio> #incl ...

  5. setStorage、getStorage、 removeStorage 封装

    // 本地存储 setStorage(name, data){ let dataType = typeof data; // json对象 if(dataType === 'object'){ win ...

  6. FZU 1575 小学生的游戏【模拟二分】

    某天,无聊的小斌叫上几个同学玩游戏,其中有比较笨的小兴,比较傻的小雪,可爱的小霞和自以为是的小楠.他们去找聪明的小明去给他们当裁判.判定谁取得游戏胜利. 而这个游戏是由小斌想个1到10000000的数 ...

  7. mysql全连接

    Oracle数据库支持full join,mysql是不支持full join的,但仍然可以同过左外连接+ union+右外连接实现 SELECT * FROM t1 LEFT JOIN t2 ON ...

  8. 会话技术之Session(购物车加入、查看和清空)

    会话技术之Session session:服务器端会话技术 当我们第一次访问的服务器的时候,服务器获取id, 能获取id 要拿着这个id去服务器中查找有无此session 若查找到了:直接拿过来将数据 ...

  9. [运维]ESXI Web Client 标签: vmware运维服务器 2017-05-28 20:59 597人阅读 评论(9)

    ESXI安装完成之后,配置好ip,我们就可以使用了?那么如何使用呢?一般来说有两种方式,一种是安装专门的管理客户端,client端,另一种更加方便,就是使用web client端. 下面来介绍一下es ...

  10. JQuery--mouseover()与moseout()的区别

    mouseover()与mouseout()区别 普通鼠标移入移出事件 语法: mouseover()/mouseout() 功能: 当鼠标移入/移出到添加事件的元素或其子元素的时候,都会被触发!!m ...