【BZOJ3131】淘金(SDOI2013)-数位DP+优先队列

测试地址:淘金
做法: 本题需要用到数位DP+优先队列。
F ( x , y ) F(x,y) F(x,y)为坐标 ( x , y ) (x,y) (x,y)上的黄金数目,那么:
F ( x , y ) = ∑ i = 1 n ∑ j = 1 n [ f ( i ) = x ] ⋅ [ f ( j ) = y ] F(x,y)=\sum_{i=1}^n\sum_{j=1}^{n}[f(i)=x]\cdot[f(j)=y] F(x,y)=i=1nj=1n[f(i)=x][f(j)=y]
= ( ∑ i = 1 n [ f ( i ) = x ] ) ⋅ ( ∑ j = 1 n [ f ( j ) = y ] ) =(\sum_{i=1}^n[f(i)=x])\cdot (\sum_{j=1}^n[f(j)=y]) =(i=1n[f(i)=x])(j=1n[f(j)=y])
g ( x ) = ∑ i = 1 n [ f ( i ) = x ] g(x)=\sum_{i=1}^n[f(i)=x] g(x)=i=1n[f(i)=x],那么我们只需要求出 g ( x ) g(x) g(x)中的前 k k k大值, F ( x , y ) = g ( x ) ⋅ g ( y ) F(x,y)=g(x)\cdot g(y) F(x,y)=g(x)g(y)中的最大值就肯定是由这些 g ( x ) g(x) g(x)组成的了。
然而对于 1 ≤ x ≤ 1 0 12 1\le x\le 10^{12} 1x1012,不同的 f ( x ) f(x) f(x)貌似很多,有 1 0 12 10^{12} 1012个?或是 9 12 9^{12} 912个(因为本题中不考虑 0 0 0)?怎么看都不可能直接进行计算。
然而实际上,我们可以证明 f ( x ) f(x) f(x)的范围远没有那么大。注意到 f ( x ) f(x) f(x)是若干个 1 1 1 ~ 9 9 9之间的数的乘积,因此它的质因子只有 2 , 3 , 5 , 7 2,3,5,7 2,3,5,7,这就已经排除掉了很多数字了。进一步地, 2 2 2最多有 36 36 36个( 12 12 12 8 8 8), 3 3 3最多有 24 24 24个( 12 12 12 9 9 9), 5 5 5 7 7 7最多有 12 12 12个, 36 × 24 × 12 × 12 = 124416 36\times 24\times 12\times 12=124416 36×24×12×12=124416,这就已经是个很小的上界了。而根据打表的结果,实际上可能达到的不同的 f ( x ) f(x) f(x)的数目只有 11015 11015 11015个。那么我们只需对于这些 f ( x ) f(x) f(x)计算对应的 g g g的值即可。
于是显然有数位DP的状态定义:令 d p ( i , j , 0 / 1 ) dp(i,j,0/1) dp(i,j,0/1)为前 i i i位,满足 f ( x ) = j f(x)=j f(x)=j的不卡/卡上界的 x x x的数量,那么转移就很容易了。显然时间复杂度可以接受。
那么我们求出了 g ( x ) g(x) g(x),用它来算前 k k k大的 F ( x , y ) F(x,y) F(x,y),就和一提高-难度经典题很像了:给两个序列 A , B A,B A,B,求 A i + B j A_i+B_j Ai+Bj的前 k k k大值。做法完全一样,用优先队列就可以解决这个问题了,时间复杂度为 O ( k log ⁡ k ) O(k\log k) O(klogk)
祝贺自己又成功想出一道黑题(虽然个人感觉没有黑题的难度),并成功冲入BZOJ时间榜第七名,可喜可贺。
以下是本人代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=1000000007;
ll x,cnt[20][20010]={0},g[20010];
int k,tot=1,st[20010][4]={0},id[40][30][20][20]={0};
int s[20],n,p[10][4]={0},pr[4]={2,3,5,7},now=1;
int nowp[20010];
struct point
{
	int id;
	ll val;
	bool operator < (point a) const
	{
		return val<a.val;
	}
};
priority_queue<point> Q;

void checkin(int i,int j,int k,ll val,bool type)
{
	int nxt[4],nxts;
	for(int l=0;l<4;l++)
		nxt[l]=st[j][l]+p[k][l];
	if (!id[nxt[0]][nxt[1]][nxt[2]][nxt[3]])
	{
		id[nxt[0]][nxt[1]][nxt[2]][nxt[3]]=++tot;
		for(int l=0;l<4;l++)
			st[tot][l]=nxt[l];
	}
	nxts=id[nxt[0]][nxt[1]][nxt[2]][nxt[3]];
	cnt[i][nxts]=(cnt[i][nxts]+val)%mod;
	if (type) now=nxts;
}

void solve()
{
	now=1;
	for(int i=n;i>=1;i--)
	{
		int lasttot=tot;
		for(int j=1;j<=lasttot;j++)
			for(int k=1;k<=9;k++)
				checkin(i,j,k,cnt[i+1][j],0);
		
		if (now)
		{
			if (i<n)
			{
				for(int j=1;j<s[i];j++)
					checkin(i,now,j,1,0);
			}
			if (s[i]==0) now=0;
			else checkin(i,now,s[i],0,1);
		}
		
		for(int j=1;j<=((i==n)?(s[i]-1):9);j++)
			checkin(i,1,j,1,0);
	}
	if (now) cnt[1][now]=(cnt[1][now]+1)%mod;
}

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

int main()
{
	id[0][0][0][0]=1;
	for(int i=1;i<=9;i++)
		for(int j=0;j<4;j++)
		{
			int x=i;
			while(x%pr[j]==0)
			{
				p[i][j]++;
				x/=pr[j];
			}
		}
	
	scanf("%lld%d",&x,&k);
	while(x)
	{
		s[++n]=x%10ll;
		x/=10ll;
	}
	solve();
	
	for(int i=1;i<=tot;i++)
		g[i]=cnt[1][i];
	sort(g+1,g+tot+1,cmp);
	for(int i=1;i<=min(k,tot);i++)
	{
		nowp[i]=1;
		point nxt;
		nxt.id=i,nxt.val=g[i]*g[nowp[i]];
		Q.push(nxt);
	}
	ll ans=0;
	for(int i=1;i<=k;i++)
	{
		if (Q.empty()) break;
		point nxt=Q.top();Q.pop();
		ans=(ans+nxt.val)%mod;
		nowp[nxt.id]++;
		if (nowp[nxt.id]<=tot)
		{
			nxt.val=g[nxt.id]*g[nowp[nxt.id]];
			Q.push(nxt);
		}
	}
	printf("%lld",ans);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值