[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。
输出格式:
一个整数,表示最多可以采集到的金子数量。
输入输出样例
说明
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; }