[SDOI2013] 淘金

题意:

有一个$n\times n$的网格,初始时每个格子上有一个金币,行列编号为$[1,n]$。

然后发生了一次操作,原来位于$(x,y)$的金币移动到了$(f_x ,f_y )$,其中$f_x$为x在十进制下各位数字的乘积。

在操作之后你可以取K个格子里的金币,问最多能取多少个,对$10^{9}+7$取模。

$n\leq 10^{12},K\leq min(n^{2},10^{5})$。

 

题解:

挺水的一道题。洛谷这个难度评级是咋回事啊QAQ

由于$1-9$只有4个质因子(只要有0肯定就不行),所以目测满足$f_x >0$的x应该不会太多。

既然这是一篇题解,我们就考虑证明一下:

设十进制下上界是k位,那么要算的就近似于从$1-9$里可以重复的选k个数字的方案数。

发现这就是可重组合模型:从非空集合$X=\{1,2,\cdots,n\}$中取r个元素,可以重复取某个元素。

该模型的方案数为${n+r-1\choose r}={n+r-1\choose n-1}$,计算可得大约$num=10000$个。

小奥理解:将r个一样的东西分到n个不同的盒子里,可以有空盒子$\rightarrow$将r+n个一样的东西分到n个不同的盒子里,不能有空盒子$\rightarrow$插板法,在r+n-1个空里插n-1个板$\rightarrow{n+r-1\choose n-1}$。

回到原题,我们只需要爆搜出所有有值的$f_x =y$,再令$c_y$表示有多少个x满足$f_x =y$,然后优先队列维护一下即可。

这个$c_y$需要数位dp求,看似复杂度是$O(num\times 状态数\times 转移数)$的,但其实不是。

由于我们的状态是$dp(n,n_2 ,n_3 ,n_5 ,n_7 )$表示已经填了前n位,每个质因子还要填$n_i$个的合法数字个数,那么每次求完一个y其实是不用清空dp数组的。

跑优先队列的时候注意不要在排序的时候取模,这会让本来单调的东西变得不单调。

复杂度$O(120num)$。

 

套路:

  • 取模时:注意如果当前算的东西对后面有影响就不要取模。
  • 可重组合数:从非空集合$X=\{1,2,\cdots,n\}$中可以重复地取r个元素$\rightarrow{n+r-1\choose r}$。
  • dp的两种设状态方式:第一种是“已经做了”,第二种是“还需要做”。

 

代码:

#include<bits/stdc++.h>
#define maxn 200005
#define maxm 500005
#define inf 0x7fffffff
#define mod 1000000007
#define ll long long
#define rint register ll
#define debug(x) cerr<<#x<<": "<<x<<endl
#define fgx cerr<<"--------------"<<endl
#define dgx cerr<<"=============="<<endl

using namespace std;
unordered_map<ll,ll> id,vis[maxn];
ll A[maxn],B[maxn][4],C[maxn],dig[13],nxt[10][4];
ll dp[15][40][26][18][15];
struct node{
    ll x,y;
    bool operator<(const node b)const{return C[x]*C[y]<C[b.x]*C[b.y];}
};
priority_queue<node> q;

inline ll read(){
    ll x=0,f=1; char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}

inline void dfs1(ll x,ll n2,ll n3,ll n5,ll n7,ll n){
    if(x>n || id[x]) return;
    A[++A[0]]=x,id[x]=A[0];
    B[A[0]][0]=n2,B[A[0]][1]=n3,B[A[0]][2]=n5,B[A[0]][3]=n7;
    dfs1(x*2,n2+1,n3,n5,n7,n),dfs1(x*3,n2,n3+1,n5,n7,n);
    dfs1(x*5,n2,n3,n5+1,n7,n),dfs1(x*7,n2,n3,n5,n7+1,n);
}

inline ll dfs2(ll n,ll n2,ll n3,ll n5,ll n7,ll ise,ll isz){
    if(n2<0 || n3<0 || n5<0 || n7<0) return 0;
    if(!n) return (!n2)&&(!n3)&&(!n5)&&(!n7)&&(!isz);
    if(dp[n][n2][n3][n5][n7]!=-1 && !ise && !isz) return dp[n][n2][n3][n5][n7];
    ll res=0;
    if(isz) res+=dfs2(n-1,n2,n3,n5,n7,0,isz);
    for(ll i=1;i<=(ise?dig[n]:9);i++){
        ll nn2=n2-nxt[i][0],nn3=n3-nxt[i][1],nn5=n5-nxt[i][2],nn7=n7-nxt[i][3];
        res+=dfs2(n-1,nn2,nn3,nn5,nn7,ise&(i==dig[n]),0);
    }
    if(!ise && !isz) dp[n][n2][n3][n5][n7]=res;
    return res;
}

inline bool cmp(ll a,ll b){return a>b;}

int main(){
    ll n=read(),K=read(),x=n;
    while(x) dig[++dig[0]]=x%10,x/=10;
    dfs1(1,0,0,0,0,n);
    nxt[2][0]=1,nxt[3][1]=1,nxt[4][0]=2,nxt[5][2]=1;
    nxt[6][0]=nxt[6][1]=1,nxt[7][3]=1,nxt[8][0]=3,nxt[9][1]=2;
    memset(dp,-1,sizeof(dp));
    for(ll i=1;i<=A[0];i++)
        C[i]=dfs2(dig[0],B[i][0],B[i][1],B[i][2],B[i][3],1,1);
    sort(C+1,C+1+A[0],cmp);
    q.push((node){1,1});
    ll ans=0;
    while(K){
        node tp=q.top(); q.pop();
        if(vis[tp.x][tp.y]) continue;
        K--,vis[tp.x][tp.y]=1,ans+=C[tp.x]*C[tp.y]%mod,ans%=mod;
        q.push((node){tp.x+1,tp.y});
        q.push((node){tp.x,tp.y+1});
    }
    printf("%lld\n",ans);
    return 0;
}
淘金

 

posted @ 2020-07-14 22:38  Fugtemypt  阅读(160)  评论(0编辑  收藏  举报