很有意思的一个题
考场上我竟然乱搞出这种奇怪的以前想都没想过的算法(以前根本想不到函数式数据结构还可以拿来DP)
其实写这个题解我也是不想这个有趣的方法就这么绝迹了呢。。。
因为是求第K大所以不难想到用堆的K路归并问题,对答案的种类我们分类,最直观的分类就是按最大因子和分解项数来分类
即用f[i,j]表示最大质因子为p[i],用了j项分解数的数的集合,因为要求数的不重不漏,我们力求让所有的数都由互素的小数集合扩展得到,为了获得之前所有的数,我们保存g[i,j]为f[i,j]的前缀和,意为前i种素因子的所有数集,不难得到DP方程:
其中加号是集合的并,只要这些集合可以归并、求最大值,就可以在外层用堆维护每个集合的最大值,每次取最大的一个,删除最大值,还可以对集合进行乘法,而这一些需求均满足可并堆的性质,因此我们用可持久化可并堆来作为数值,进行DP。
可并堆的乘法用标记实现,标记下传时新建节点即可。注意中间过程可能爆long long,乘法要判>0防炸。
#include<queue> #include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std; typedef long long LL; struct node{ int i,k;LL v; node(int a,int b,LL c){i=a;k=b;v=c;} bool operator<(node b)const{return v<b.v;} }; priority_queue<node>q; struct Heap{LL v,tg;int l,r;}c[16000005]; LL N; int K,tot=0,p[505],vst[505],f[505][505],g[505][505]; int NewNode(int x,LL tg){ if(x==0)return 0; tot++;c[tot]=c[x];c[tot].v*=tg;c[tot].tg*=tg; return tot; } void Pushdown(int x){ if(c[x].tg!=1){ c[x].l=NewNode(c[x].l,c[x].tg); c[x].r=NewNode(c[x].r,c[x].tg); c[x].tg=1; } } int merge(int x,int y){ if(!x||!y)return x+y; Pushdown(x);Pushdown(y); if(c[x].v<c[y].v)swap(x,y); int z=++tot;c[z]=c[x]; c[z].l=merge(c[x].r,y); c[z].r=c[x].l;c[z].tg=1; return z; } void init(){ int i,j,k;LL pr,prm; scanf("%lld%d",&N,&K); for(i=2;i<=128;i++) if(!vst[i]){ p[++p[0]]=i; for(j=i+i;j<=128;j+=i)vst[j]=1; } f[0][0]=1;tot=1;g[0][0]=1; c[f[0][0]].v=c[f[0][0]].tg=1; for(i=1;i<=p[0];i++){ f[i][0]=f[0][0];g[i][0]=1; for(pr=p[i],j=1;pr>0&&pr<=N;j++,pr=pr*p[i]){ f[i][j]=0; for(prm=p[i],k=1;k<=j;k++,prm*=p[i]) f[i][j]=merge(f[i][j],NewNode(g[i-1][j-k],prm)); q.push(node(i,j,c[f[i][j]].v)); g[i][j]=merge(g[i-1][j],f[i][j]); } } } void solve(){ int i,j; for(i=1;i<=K;i++){ node x=q.top();q.pop(); if(i==K){ printf("%lld\n",x.v); return; } Pushdown(f[x.i][x.k]); f[x.i][x.k]=merge(c[f[x.i][x.k]].l,c[f[x.i][x.k]].r); q.push(node(x.i,x.k,c[f[x.i][x.k]].v)); } } int main(){ init(); solve(); return 0; }