【CF 908G】New Year and Original Order

洛谷链接

题目描述

n &lt; = 1 0 700 n&lt;=10^{700} n<=10700,问1到n中每个数在各数位排序后得到的数的和。答案膜1e9+7。

Sol

神仙套路:
假设 X = a n a n − 1 a n − 2 … a 1 ‾ X=\overline{a_na_{n-1}a_{n-2}\dots a_1} X=anan1an2a1是一个每个位置上的数都严格上升
那么可以把 X X X表示为 ∑ d [ i ] ∗ 1111111 … 11 ‾ 共 i 个 1 \sum d[i]*\underline {1111111\dots11}_{共i个1} d[i]111111111i1其中 d [ i ] d[i] d[i]表示第 i i i 为和第 i + 1 i+1 i+1 的差值(其实任何一个数都能这样表示)

然后我们可以把数 X X X理解为一个阶梯状的东西,强行把差值换一种解释:

d [ i ] = d[i]= d[i]= k ∈ [ 0 , 9 ] k\in[0,9] k[0,9] 的个数 , 满足在所有的数位中 , 大于数 k k k 的数的个数恰好有 i i i

然后就直接暴力设状态 d p [ 0 / 1 ] [ 0 … 9 ] [ i ] [ j ] dp[0/1][0\dots9][i][j] dp[0/1][09][i][j] 表示到了第 i i i 位 , 安全态还是危险态下 , 大于数 k k k 的位数有 j j j 个的方案数,最后算贡献

为什么要记录方案数? 因为我们已经知道每一个 d [ i ] d[i] d[i] 要乘上的系数了,只需要求出每一种情况下的 d [ i ] d[i] d[i] , 而每一种情况下的贡献是独立的,不会重复 , 因此只要算出方案数就行了

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<queue>
#include<cmath>
#include<algorithm>
#include<set>
using namespace std;
const int N=800;
const int mod=1e9+7;
char s[N];
int a[N],len,ans=0;
int dp[2][10][N][N];// 是否安全态下 到了第 i 位 大于 数字 k 的有 j 个的方案数
int fac[N];
typedef long long ll;
inline void upd(int &x,int y){x+=y;if(x>=mod) x-=mod;}
int main()
{
	scanf("%s",s+1);
	len=strlen(s+1);
	for(register int i=1;i<=len;++i) a[i]=s[i]-'0';
	fac[1]=1;
	for(register int i=2;i<=len;++i) fac[i]=(1ll*fac[i-1]*10+1)%mod;
	for(register int i=0;i<=9;++i) dp[1][i][0][0]=1;
	for(register int i=1;i<=len;++i) {
		for(register int p=0;p<=9;++p){
			for(register int j=0;j<=i;++j){
				for(register int k=0;k<=9;++k){
					if(p>k) {
						if(j==0) continue;
						upd(dp[0][k][i][j],dp[0][k][i-1][j-1]);
						if(p<a[i]) upd(dp[0][k][i][j],dp[1][k][i-1][j-1]);
						if(p==a[i]) upd(dp[1][k][i][j],dp[1][k][i-1][j-1]);
					}
					else{
						upd(dp[0][k][i][j],dp[0][k][i-1][j]);
						if(p<a[i]) upd(dp[0][k][i][j],dp[1][k][i-1][j]);
						if(p==a[i]) upd(dp[1][k][i][j],dp[1][k][i-1][j]);
					}
					
				}
			}
		}
	}
	register int ans=0;
	for(register int i=1;i<=len;++i){
		
		for(register int j=0;j<=9;++j){
			upd(ans,1ll*fac[i]*(dp[0][j][len][i]+dp[1][j][len][i])%mod);
		}
	}
	printf("%d\n",ans);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值