其他分享
首页 > 其他分享> > [SDOI2013] 淘金(数位dp+堆)

[SDOI2013] 淘金(数位dp+堆)

作者:互联网

description

\(f(x)\)表示\(x\)的各个数位的乘积。
给\(N*N\)的矩阵每个矩阵上都有一块金子,一次变化后,金子从\((i,j)\)变到\((f(i),f(j))\)。
问一次变化后矩阵上金子个数前\(k\)大的和为多少。
\(N<=10^{12}\)

solution

code:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=15001;
const ll mod=1e9+7;
map<ll,int>mp;
ll n,pm[N];
int m,k;

void init() {
	ll w=1;
	for(int i=0;i<=39;i++) {
		ll x=w;
		for(int j=0;j<=25;j++) {
			ll y=w;
			for(int l=0;l<=17;l++) {
				ll z=w;
				for(int r=0;r<=14;r++) {
					pm[mp[w]=++m]=w;
					w*=7;
					if(w>n) break;
				}
				w=z*5;
				if(w>n) break;
			}
			w=y*3;
			if(w>n)break;
		}
		w=x*2;
		if(w>n)break;
	}
}
ll dp[17][N][2];
int len,a[N];
ll c[N];		//乘积为$i$对应的方案数
void DP() {
	ll x=n;
	while(x) {a[++len]=x%10;x/=10;}
	for(int j=1;j<=9;j++) {dp[1][mp[j]][j>a[1]]=1;}
	for(int i=2;i<=len;i++) {
		for(int j=1;j<=m;j++) {
			ll w=pm[j];
			for(int k=1;k<=9;k++) {
				if(w%k)continue;
				int o=mp[w/k];
				if(k<a[i]) {dp[i][j][0]+=dp[i-1][o][0]+dp[i-1][o][1];}
				else if(k>a[i]) {dp[i][j][1]+=dp[i-1][o][0]+dp[i-1][o][1];}
				else {
					dp[i][j][0]+=dp[i-1][o][0];
					dp[i][j][1]+=dp[i-1][o][1];
				}
			}
		}
	}
	for(int i=1;i<=len;i++)for(int j=1;j<=m;j++) {
		c[j]+=dp[i][j][0]+((i==len)?0:dp[i][j][1]);
	}
}

struct pq {
	int x,y;
	bool operator <(const pq &u) const{return c[x]*c[y]<c[u.x]*c[u.y];}
};
priority_queue<pq> Q;

void solve() {
	k=min((ll)k,(ll)m*m);
	sort(c+1,c+1+m);
	for(int i=1;i<=m;i++)Q.push((pq){i,m});
	ll ans=0;
	while(!Q.empty()&&k--) {
		int x=Q.top().x,y=Q.top().y; Q.pop();
		ans=(ans+c[x]*c[y])%mod;
		if(y>1)Q.push((pq){x,y-1});
	}
	printf("%lld",ans);
}

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

标签:乘积,int,ll,break,SDOI2013,dp,数位
来源: https://www.cnblogs.com/bestime/p/16475885.html