K倍数字

K倍数字

题面:5dZEEq.png


根据题目可知 \(f(x)\) 跟数字每一位数位上的数字有关,我们考虑数位 dp。

传统的数位 dp 习惯从高位到低位,我们设 \(dp[i][j][o][l][m]\) 表示当前遍历到第 \(i\) 位,目前个数字之和是 \(j\),乘 \(k\) 后的个数字之和是 \(o\),乘 \(k\) 后的产生的进位是 \(l\)\(m\) 表示是否有限制。

这样显然会 MLE 。我们考虑将 \(j,o\) 合并成一维,因为我们最后只用通过 \(j,o\) 是否相等来判断这个数字是否可行,所以我们可以将 \(j,o\) 合成一维,这一位上的数字表示 \(j,o\) 的差值。

然后我们发现,当前位乘 \(k\) 后产生的实际上的会对 \(i\) 前面那几位产生影响。 所以我们从高位到低位 dp 的话,低位的逐渐会对高位的状态产生影响。所以我们不能从高位往低位 dp。但是从低位往高位 dp 就可以消除这种影响。但是低位往高位 dp 的话,我们得考虑 \(limit\) 标记(限制标记)怎么处理,我们用 \(limit\) 表示当前的数是否超过 \(n\),然后用 \(pos\) 表示当前枚举到了哪一位,\(i\) 表示当前枚举的数字,\(a\) 数组表示存 \(n\) 的各位分解。那么 \(limit\) 的传递就是:

\[(i>a[pos]||(limit\&\&i==a[pos])) \]

附赠 realman 的 txt。

5dmP7n.png

然后就是一个数位 dp 的板子:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int MAXN =1e3+5;
ll n,k,dp[20][1000][400][2];
int a[20],all,cnt[MAXN];
ll dfs(int pos,bool limit,int tot,int d)
{
	if(pos==all+1) return (!limit&&tot-cnt[d]==200);
	if(dp[pos][d][tot][limit]!=-1) return dp[pos][d][tot][limit];
	ll ans=0;
	for(int i=0;i<10;++i)
		ans+=dfs(pos+1,(i>a[pos])||(limit&&i==a[pos]),tot+i-(d+i*k)%10,(d+i*k)/10);
	return dp[pos][d][tot][limit]=ans;
}
ll solve(ll x)
{
	while(x)
	{
		a[++all]=x%10;
		x/=10;
	}
	memset(dp,-1,sizeof dp);
	return dfs(1,0,200,0)-1;
}
int main()
{
//	freopen("num.in","r",stdin);
//	freopen("num.out","w",stdout);
	scanf("%lld %lld",&n,&k);
	for(int i=1;i<=1000;++i) cnt[i]=cnt[i/10]+i%10;
	printf("%lld\n",solve(n));
	return 0;
}

按照 pp 的说法,套路题

posted @ 2021-10-19 10:54  夜空之星  阅读(81)  评论(0编辑  收藏  举报