[数位DP]BZOJ 3131 [Sdoi2013]淘金 题解

题目大意

有一个大小为 N ∗ N N*N NN的矩阵,一开始矩阵内每一个数都是1,每一次变换后坐标为 ( i , j ) (i,j) (i,j)内的数会累加到 ( f ( i ) , f ( j ) ) (f(i),f(j)) (f(i),f(j)),其中 f ( x ) f(x) f(x)为各位数的累积(如果 f ( x ) = 0 f(x)=0 f(x)=0,那么直接消失)。问一次变换后矩阵内前 K K K大的数之和。

N ≤ 1 0 12 , K ≤ 1 0 6 N\le10^{12},K\le10^6 N1012,K106

解题分析

首先定义 d i d_i di为在 [ 1 , N ] [1,N] [1,N]内乘积为i的数的个数,那么 ( i , j ) (i,j) (i,j)的个数为 d i ∗ d j d_i*d_j didj,所以先算出 d i d_i di

由于 f ( x ) f(x) f(x)函数累积的数都小于10,所以对于任何一个 f ( x ) f(x) f(x),最多只有2,3,5,7四个素数,一枚举发现这种数在 1 0 12 10^{12} 1012内只有14672个,所以用数位DP累加答案,然后利用堆优化就好了。

示例代码

题目传送门

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=15000,flg[4]={2,3,5,7},tt=1e9+7;
int m,q,c[20],pos[maxn],len,ans;
LL n,a[4][50],b[maxn],nxt[maxn][10],f[20][maxn][2],d[maxn];
int _find(LL x){
	int L=1,R=m,mid;
	while (L<=R){
		int mid=L+(R-L>>1);
		if (b[mid]==x) return mid;
		if (b[mid]<x) L=mid+1; else R=mid-1;
	}
	return 0;
}
struct data{
	LL te; int x;
	data (LL te=0,int x=0):te(te),x(x){}
	bool operator < (const data b)const{return te>b.te;}
}hep[maxn];
void Hput(data a){
	hep[++len]=a;
	for (int son=len;hep[son]<hep[son>>1]&&son!=1;son>>=1) swap(hep[son],hep[son>>1]);
}
data Hget(){
	data ans=hep[1]; hep[1]=hep[len--];
	for (int fa=1,son;(fa<<1)<=len;fa=son){
		son=fa<<1; if (son<len&&hep[son|1]<hep[son]) son++;
		if (hep[son]<hep[fa]) swap(hep[son],hep[fa]); else break;
	}
	return ans;
}
void makep(){
	bool vs[4]; memset(vs,1,sizeof(vs)); int te=4;
	for (int j=0;j<4;j++) a[j][0]=a[j][1]=1;
	while (te){
		for (int j=0;j<4;j++)
			if (a[j][a[j][0]]*flg[j]<=n){a[j][a[j][0]+1]=a[j][a[j][0]]*flg[j]; a[j][0]++;}
			else {te-=vs[j]; vs[j]=0;}
	}
	for (int i=1;a[0][i]<=n&&i<=a[0][0];i++)
		for (int j=1;a[0][i]*a[1][j]<=n&&j<=a[1][0];j++)
			for (int k=1;a[0][i]*a[1][j]*a[2][k]<=n&&k<=a[2][0];k++)
				for (int p=1;a[0][i]*a[1][j]*a[2][k]*a[3][p]<=n&&p<=a[3][0];p++)
					b[++m]=a[0][i]*a[1][j]*a[2][k]*a[3][p];
	sort(b+1,b+m+1); m=unique(b+1,b+m+1)-b-1;
	for (int i=1;i<=m;i++)
		for (int j=1;j<10;j++) nxt[i][j]=_find(b[i]*j);
	LL x=n; do{c[++c[0]]=x%10; x/=10;}while(x);
}
int main()
{
	freopen("gold.in","r",stdin);
	freopen("gold.out","w",stdout);
	scanf("%lld%d",&n,&q); makep(); f[c[0]+1][1][1]=1;
	for (int i=c[0];i;i>1?f[i--][1][0]++:i--)
		for (int t=0;t<2;t++)
			for (int j=1;j<=m;j++)
				for (int k=1,mx=(t?c[i]:9);k<=mx;k++)
					if (nxt[j][k]) f[i][nxt[j][k]][t&&k==mx]+=f[i+1][j][t];
	for (int i=1;i<=m;i++) d[i]=-f[1][i][0]-f[1][i][1]; sort(d+1,d+m+1);
	ans=len=0; for (int i=1;i<=m;i++) Hput(data(d[i]*d[pos[i]=1],i));
	while (q--){data p=Hget(); int x=p.x; ans=(ans+p.te%tt)%tt; Hput(data(d[x]*d[++pos[x]],x));}
	printf("%d",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值