bzoj 3131: [Sdoi2013]淘金 数位dp+堆

时间:2022-12-31 16:42:14

题意

小Z在玩一个叫做《淘金者》的游戏。游戏的世界是一个二维坐标。X轴、Y轴坐标范围均为1..N。初始的时候,所有的整数坐标点上均有一块金子,共N*N块。
一阵风吹过,金子的位置发生了一些变化。细心的小Z发现,初始在(i,j)坐标处的金子会变到(f(i),f(j))坐标处。其中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 < = 10^12 ,K < = 100000

分析

注意到一个点的坐标(x,y)的金子数量就等于s(x)*s(y),其中s(x)表示有多少个不大于的整数其各个位乘积为x。那么只要求出所有的s(x)即可。
由于s(x)中的x仍然很大,所以不能直接dp。注意到假如s(x)不为0,则x必然只含2,3,5,7这4个质因数,那么我们就可以考虑对素因数的指数进行数位dp。
设f[i,x1,x2,x3,x4,0/1]表示从高到底做到n的第i位,2的指数为x1,3的指数为x2,5的指数为x3,7的指数为x4且是否卡着n的上界的数的数量。直接瞎转移即可。
求出所有的s(x),可以用堆来维护两个数乘积的前k大。因为我比较懒所以直接用map来判断数对(x,y)是否出现过。

由于一开始没有想清楚就开始打,导致后面发现很多地方都白打了。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#include<map>
using namespace std;

typedef long long LL;

const int MOD=1000000007;

int m,tr[10][5],f[2][45][45][45][45][2],a[15],val[45*45*45*45];
LL n;
priority_queue<pair<LL,pair<int,int> > > que;
map<pair<int,int>,bool> vis;

void updata(int &x,int y)
{
    x+=y;
}

void prework()
{
    tr[2][1]=1;
    tr[3][2]=1;
    tr[4][1]=2;
    tr[5][3]=1;
    tr[6][1]=tr[6][2]=1;
    tr[7][4]=1;
    tr[8][1]=3;
    tr[9][2]=2;
}

int main()
{
    prework();
    scanf("%lld%d",&n,&m);
    int t1=0,t2=0,t3=0,t4=0;LL t;
    t=1;while (t*2<=n) t1++,t=t*2;
    t=1;while (t*3<=n) t2++,t=t*3;
    t=1;while (t*5<=n) t3++,t=t*5;
    t=1;while (t*7<=n) t4++,t=t*7;
    int now=0,a1=0;LL tmp=n;
    while (tmp) a[++a1]=tmp%10,tmp/=10;
    f[now][0][0][0][0][1]=1;
    for (int i=a1;i>=1;i--)
    {
        memset(f[now^1],0,sizeof(f[now^1]));
        for (int x1=t1;x1>=0;x1--)
            for (int x2=t2;x2>=0;x2--)
                for (int x3=t3;x3>=0;x3--)
                    for (int x4=t4;x4>=0;x4--)
                    {
                        if (f[now][x1][x2][x3][x4][0])
                        {
                            for (int j=1;j<=9;j++) updata(f[now^1][x1+tr[j][1]][x2+tr[j][2]][x3+tr[j][3]][x4+tr[j][4]][0],f[now][x1][x2][x3][x4][0]);
                        }
                        if (f[now][x1][x2][x3][x4][1])
                        {
                            if (a[i]) updata(f[now^1][x1+tr[a[i]][1]][x2+tr[a[i]][2]][x3+tr[a[i]][3]][x4+tr[a[i]][4]][1],f[now][x1][x2][x3][x4][1]);
                            for (int j=1;j<a[i];j++) updata(f[now^1][x1+tr[j][1]][x2+tr[j][2]][x3+tr[j][3]][x4+tr[j][4]][0],f[now][x1][x2][x3][x4][1]);
                        }
                    }
        now^=1;
        f[now][0][0][0][0][0]++;
    }
    int tot=0;
    f[now][0][0][0][0][0]--;
    for (int i=0;i<=t1;i++)
        for (int j=0;j<=t2;j++)
            for (int k=0;k<=t3;k++)
                for (int l=0;l<=t4;l++)
                {
                    int s=f[now][i][j][k][l][0]+f[now][i][j][k][l][1];
                    if (s) val[++tot]=s;
                }
    sort(val+1,val+tot+1);
    que.push(make_pair((LL)val[tot]*val[tot],make_pair(tot,tot)));
    LL ans=0;
    while (m--)
    {
        if (que.empty()) break;
        pair<LL,pair<int,int> > u=que.top();que.pop();
        (ans+=u.first)%=MOD;
        int x=u.second.first,y=u.second.second;
        if (y&&!vis[make_pair(x,y-1)]) que.push(make_pair((LL)val[x]*val[y-1],make_pair(x,y-1))),vis[make_pair(x,y-1)]=1;
        if (x&&!vis[make_pair(x-1,y)]) que.push(make_pair((LL)val[x-1]*val[y],make_pair(x-1,y))),vis[make_pair(x-1,y)]=1;
    }
    printf("%lld",ans);
    return 0;
}