大意:(不好描述,看题目吧)
时间复杂度,一定是要依托于k来计算,看似要枚举的东西很乱,细细的规划一下,可以想到把他们以一个二元组区分(p,size)
表示一个数的最大素数为p,且为size个素数相乘,一开始预处理,就可以求出2-127这31个质数的最大size,即 pi^size[i]>=N(size[i]取到最小)(这样能保证之后的所有状态的权值都是小于N的),一开始,我们的状态数就只有sigma(size[i]) ,1<=i<=31(表示第i个质数) ,即一开始时,每一个二元组只有一个有效状态,(该二元组的其他所有状态一定没有当前状态值优),且这些状态表示的序列都为 size 个 p。该状态的权值即为pi^size[i]。因为要频繁询问当前最大权值和具有修改操作,我选择了可并堆。每一次,取出可并堆的堆顶元素,即为最大权值,表示我现在选择了它,可以找到它原来的那个序列,那么现在就要利用这个序列扩充新的序列。设size为这个二元组的size,p为这个二元组的p,那么相当于是要找到size个素数相乘,且最大素数为p。规定要用最小表示法,即这size个素数单调不下降组成一个序列,这样,我们就要枚举将其中的哪一个素数变小,它变化后不能破坏这个序列单调性,在枚举完后,会有一个新的权值(可以O(1)计算得),它可能已经在前面搜索中进堆了(举例来说,对于一个二元组(7,5),最初始为7*7*7*7*7,它接下来会搜索到序列(不是直接枚举到) 5*5*7*7*7 (记为f1)和3*7*7*7*7(记为f2) 显然f1会f2先出堆,f1 会搜到序列 3*5*7*7*7(记为f3),那么f3已经入堆了,之后如果f2出堆了,那么它也会枚举到f3,但此时f3已经不能入堆,否则将有一些数被算重),因此这里加一个哈希表来判。以此处理,每次出堆会算出当前最大值,且最多会多进入60个新的序列(其实远远达不到),那么时间复杂度就是多项式,O(k*logm)m为可并堆里面的元素个数。数组可能会吃紧,考虑到一个序列出堆后就没用了,可以写个回收站来回收空间以图再利用(可并堆里的元素也是如此)。这样虽然代码长了点,也能很好的解决这个问题了(ORZ%%%%%搜索+减枝过的),可能叙述有些难懂,代码还是很好理解的。(代码里用i表示第i个素数pi)
#include<iostream>
#include<iomanip>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long LL;
const LL N=650005,mod=1000003;
LL prime[200],num=0,size[200],m;
LL jilu[N],Add=0,He=0;
LL tmp[N]={0},top=0;
struct data{LL size,i;}P[2500];LL cnt=0;
LL map[N][62];
struct node{LL L,R,val,i;}tr[N];LL sign=0,root;
bool vst[200];
struct Haxi
{
LL h[1000005],tot;
struct qxx{LL to,next;}q[8000005];
LL Ask(LL val)
{
LL i,x=val%mod;
for(i=h[x];i;i=q[i].next)
if(q[i].to==val)return 1;
return -1;
}
LL Insert(LL val)
{
LL i,x=val%mod;
tot++;
q[tot].to=val;q[tot].next=h[x];h[x]=tot;
}
}pq;
LL CNTNUM()
{
if(Add)return jilu[Add--];
return ++He;
}
LL CNTclean(LL x){jilu[++Add]=x;}
LL Numb()
{
if(top)return tmp[top--];
return ++sign;
}
void Clean(LL x)
{
tr[x].L=tr[x].R=tr[x].i=tr[x].val=0;
tmp[++top]=x;
}
void Build()
{
LL i,j,x;
for(i=2;i<=128;i++)
{
if(!vst[i])prime[++num]=i;
for(j=1;j<=num;j++)
{
if(i*prime[j]>128)break;
vst[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
LL n;
scanf("%lld%lld",&n,&m);
for(i=1;i<=num;i++)
{
x=n;j=0;
while(x>=prime[i]){x/=prime[i];j++;}
size[i]=j;
}
}
LL Ksm(LL a,LL b)
{
LL ans=1;
while(b)
{
if(b&1)ans=ans*a;
a=a*a;
b>>=1;
}
return ans;
}
LL Merge(LL x,LL y)
{
if(!x||!y)return x+y;
if(tr[x].val<tr[y].val)swap(x,y);
tr[x].R=Merge(tr[x].R,y);
swap(tr[x].L,tr[x].R);
return x;
}
void Pop()
{
LL x=root;
root=Merge(tr[root].L,tr[root].R);
Clean(x);
}
void Solve()
{
LL i,j,x,k;
pq.tot=0;
for(i=1;i<=num;i++)
for(j=1;j<=size[i];j++)
{
cnt=CNTNUM();P[cnt].size=j;P[cnt].i=i;
}
LL nowval;
root=0;
for(i=1;i<=He;i++)
{
nowval=Ksm(prime[P[i].i],P[i].size);
for(j=1;j<=P[i].size;j++)map[i][j]=P[i].i;map[i][0]=P[i].size;
x=Numb();
tr[x].L=tr[x].R=0;tr[x].i=i;tr[x].val=nowval;
pq.Insert(nowval);
root=Merge(root,x);
}
LL Ti,bj,Toval;
for(Ti=1;Ti<=m;Ti++)
{
i=tr[root].i;nowval=tr[root].val;
if(Ti==m){printf("%lld",nowval);break;}
Pop();
for(j=1;j<=map[i][0]-1;j++)
{
if(map[i][j]>1&&(map[i][j]>map[i][j-1]||j==1))
{
Toval=nowval;
Toval=Toval/prime[map[i][j]]*prime[map[i][j]-1];
if(pq.Ask(Toval)==-1)pq.Insert(Toval);
else continue;
cnt=CNTNUM();
for(k=0;k<=map[i][0];k++)map[cnt][k]=map[i][k];
map[cnt][j]--;
x=Numb();
tr[x].L=tr[x].R=0;tr[x].i=cnt;tr[x].val=Toval;
root=Merge(root,x);
}
}
CNTclean(i);
}
}
int main()
{
Build();
Solve();
return 0;
}