BZOJ4524 [CQOI2016]伪光滑数 可持久化可并堆+DP

很有意思的一个题

考场上我竟然乱搞出这种奇怪的以前想都没想过的算法(以前根本想不到函数式数据结构还可以拿来DP)

其实写这个题解我也是不想这个有趣的方法就这么绝迹了呢。。。

因为是求第K大所以不难想到用堆的K路归并问题,对答案的种类我们分类,最直观的分类就是按最大因子和分解项数来分类

即用f[i,j]表示最大质因子为p[i],用了j项分解数的数的集合,因为要求数的不重不漏,我们力求让所有的数都由互素的小数集合扩展得到,为了获得之前所有的数,我们保存g[i,j]为f[i,j]的前缀和,意为前i种素因子的所有数集,不难得到DP方程:

BZOJ4524 [CQOI2016]伪光滑数 可持久化可并堆+DP_第1张图片


其中加号是集合的并,只要这些集合可以归并、求最大值,就可以在外层用堆维护每个集合的最大值,每次取最大的一个,删除最大值,还可以对集合进行乘法,而这一些需求均满足可并堆的性质,因此我们用可持久化可并堆来作为数值,进行DP。

可并堆的乘法用标记实现,标记下传时新建节点即可。注意中间过程可能爆long long,乘法要判>0防炸。

#include
#include
#include
#include
#include
#include
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 vq;
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].v0&&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;
}


你可能感兴趣的:(可持久化可并堆,DP)