[SDOI2013] 淘金

题目描述

小Z在玩一个叫做《淘金者》的游戏。游戏的世界是一个二维坐标。X轴、Y轴坐标范围均为1..N。初始的时候,所有的整数坐标点上均有一块金子,共N*N块。

一阵风吹过,金子的位置发生了一些变化。细心的小Z发现,初始在(i,j)坐标处的金子会变到(f(i),fIj))坐标处。其中f(x)表示x各位数字的乘积,例如f(99)=81,f(12)=2,f(10)=0。

如果金子变化后的坐标不在1..N的范围内,我们认为这块金子已经被移出游戏。同时可以发现,对于变化之后的游戏局面,某些坐标上的金子数量可能不止一块,而另外一些坐标上可能已经没有金子。这次变化之后,游戏将不会再对金子的位置和数量进行改变,玩家可以开始进行采集工作。

小Z很懒,打算只进行K次采集。每次采集可以得到某一个坐标上的所有金子,采集之后,该坐标上的金子数变为0。

现在小Z希望知道,对于变化之后的游戏局面,在采集次数为K的前提下,最多可以采集到多少块金子? 答案可能很大,小Z希望得到对1000000007(10^9+7)取模之后的答案。

输入输出格式

输入格式:

 

共一行,包含两介正整数N,K。

 

输出格式:

 

一个整数,表示最多可以采集到的金子数量。

 

输入输出样例

输入样例#1: 
12  5
输出样例#1: 
18

说明

N < = 10^12 ,K < = 100000

对于100%的测试数据:K < = N^2

 

(为什么我的代码又这么长啊2333,到底是因为 码风不再简约 还是因为最近做的题目都太毒瘤了啊??)

 

一:统计一维的点的答案

    首先可以发现 f() 的取值并不是很多,因为其的质因子集合只能是 {2,3,5,7} 的一个子集,搜一搜发现n=1e12的时候 f() 的取值只有不到 15000 种。。。

    于是就可以直接数位dp啦,f[i][j][0\1] 表示考虑了从高到低前i为,目前数字的乘积是 num[j] ,并且是(1)否(0)贴上界的数的个数有多少。

    可以先预处理一个数组 to[i][1~9],表示num[i] * 1~9 会到 num[to[i][0~9]],转移的时候根据这个数组转移就好啦。。。。

    不过还需要增设一个状态0,表示目前还没有选数的状态(因为选的数可能位数比n少),0状态只能从自己过来,并且出边和num[] = 1的状态是一样的。

    

    所以这个数位dp貌似是 O(12 * 15000 * 9)的? 实际还要小一点,因为加了一些小剪枝。。。。

 

二:一维转化成二维,计算最后答案

    我们设最后 f(x) = num[i] 的 x 有 c[i] 个,那么 最后的问题就是,选k对 (i,j) ,使得 ∑ c[i] * c[j] 最大。

    这貌似是一个 二分+two pointer 的模板题,,只不过因为c[] * c[]在大数据会爆long long,所以我取了一下对数,把整数乘法变成了实数加法。

     按理说实数运算总是会有点误差的,更何况我还没用long double,但最后莫名其妙的就A 了2333,并且还是bzoj 这个题的rank1,好迷啊。。。。

 

 

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const double eps=1e-11;
const int maxn=15005,ha=1e9+7;
inline int add(int x,int y){ x+=y; return x>=ha?x-ha:x;}
inline void ADD(int &x,int y){ x+=y; if(x>=ha) x-=ha;}
int to[maxn][10],k,ans,cnt,a[15],len,hz[maxn];
ll n,num[maxn],f[15][maxn][2],c[maxn];
double b[maxn],L,R,mid;

inline void init(){
	for(ll A=1;A<=n;A*=7ll)
	    for(ll B=A;B<=n;B*=5ll)
	        for(ll C=B;C<=n;C*=3ll)
	            for(ll D=C;D<=n;D*=2ll) num[++cnt]=D;
	
	sort(num+1,num+cnt+1);
	
	for(int i=1;i<=cnt;i++){
		to[i][1]=i;
		ll T;
		for(int j=2;j<=9;j++){
			T=num[i]*(ll)j;
			if(T>num[cnt]) break;
			to[i][j]=lower_bound(num+1,num+cnt+1,T)-num;
		}
	}
	
	for(int i=1;i<=9;i++) to[0][i]=i;
}

inline void dp(){
	while(n) a[++len]=n%10,n/=10;
	reverse(a+1,a+len+1);
	
	f[0][0][1]=1;
	for(int i=1;i<=len;i++){
		
		f[i][0][0]=f[i-1][0][1]+f[i-1][0][0];
		
	    for(int j=0;j<=cnt;j++) if(f[i-1][j][0]||f[i-1][j][1]){
	    	
	    	for(int l=1,T;l<=9;l++){
	    		T=to[j][l];
	    		if(!T) continue;
	    		
	    		f[i][T][0]+=f[i-1][j][0];
	    		if(l<a[i]) f[i][T][0]+=f[i-1][j][1];
	    		else if(l==a[i]) f[i][T][1]+=f[i-1][j][1];
			}
		}
		
	}
	
}

inline ll count(){
	ll an=0;
	int l=1;
	
	for(int i=cnt;i;i--){
		while(l<cnt&&b[l]+b[i]+eps<mid) l++;
		if(b[l]+b[i]+eps<mid) break;
		an+=(ll)(cnt-l+1);
	}
	
	return an;
}

inline void calc(){
	for(int i=1;i<=cnt;i++) c[i]=f[len][i][0]+f[len][i][1];
	sort(c+1,c+cnt+1);
	for(int i=1;i<=cnt;i++){
	    if(c[i]) b[i]=log(c[i]);
	    else b[i]=-233;
	    R=max(R,b[i]);
	}
	
	L=-233;R*=2;
	while(R-L>=eps){
		mid=(L+R)/2;
		if(count()>=k) L=mid;
		else R=mid;
	}
	
	for(int i=1;i<=cnt;i++) c[i]%=ha;
	for(int i=cnt;i;i--) hz[i]=add(hz[i+1],c[i]);
	
	ll N=0;
	int cj=0,l=1;
	
	for(int i=cnt;i;i--){
		while(l<cnt&&b[l]+b[i]+eps<L) l++;
		
		if(b[l]+b[i]+eps<L) break;
		else if(fabs(b[l]+b[i]-L)<=eps) cj=c[i]*(ll)c[l]%ha;
		
		N+=(ll)(cnt-l+1);
		ADD(ans,c[i]*(ll)hz[l]%ha);
	}
	
	ADD(ans,ha-cj*(ll)(N-k)%ha);
}

inline void solve(){
	dp();
	calc();
} 

int main(){
	scanf("%lld%d",&n,&k),init();
	solve(),printf("%d\n",ans);
	
	return 0;
}

  

posted @ 2018-05-24 13:04  蒟蒻JHY  阅读(282)  评论(0编辑  收藏  举报