[CodeForces908G]New Year and Original Order

本文介绍了一种利用数位动态规划(DP)解决特定类型数学问题的方法,特别是当涉及大整数时。通过具体示例展示了如何将复杂问题简化,并提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:定义函数S(x)S(x)是把xx各数位排序后得到的数,求i=1nS(i)

S(2018)=128,S(998244353)=233445899S(2018)=128,S(998244353)=233445899

n10700n≤10700


看着nn这么大显然是数位DP

扒一下官方题解


This is a digit dpdp problem. Let’s try to solve the subproblem “How many ways can theithi−th digit be at least jj?”. Let’s fix j, and solve this with dpdp. We have a dp state dp[a][b][c]dp[a][b][c]=number of ways given we’ve considered the first a digits of XX, we need b more occurrences of digits at least jj, and c is a boolean saying whether or not we are strictly less than XX or not yet.

For a fixed digit, we can compute this dp table in O(n2) time, and then compute the answers to our subproblem for each ii (i.e.1 by varying bb in our table).

1.i.e. in other words/this is


考虑到原问题很难做,我们转换一下问题

举个例子:假设第ii位填3,那么他的贡献的310i3∗10i,考虑把这个贡献拆开

33,32,313≥3,3≥2,3≥1我们在这一位填1,2,31,2,3时都记上10i10i的贡献

那么我们就只要记这一位填大于等于某个数的方案数就好了

考虑按照套路dp[i][j][k][l]dp[i][j][k][l]表示前ii位有j位的数字大小大于等于kk,是否严格小于n的方案数

枚举第i+1i+1位填pp

f[i+1][j+(p>=k)][k][l|(p<ai+1)]=f[i][j][k][l]

然后实际上假设前nn位我们j位数字大于等于kk的方案数是sum=f[n][j][k][0]+f[n][j][k][1]

这个对答案的贡献的是sum11111j1sum∗111…11⏟j个1

 #include<bits/stdc++.h>
#define fp(i,a,b) for(register int i=a,I=b+1;i<I;++i)
#define fd(i,a,b) for(register int i=a,I=b-1;i>I;--i)
#define go(u) for(register int i=fi[u],v=e[i].to;i;v=e[i=e[i].nx].to)
#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)
using namespace std;
const int N=705,P=1e9+7;
int n,ans,a[N],f[N][N][10][2];char s[N];
inline void add(int&a,int b){a+=b,a>=P?a-=P:0;}
int main(){
    #ifndef ONLINE_JUDGE
        file("s");
    #endif
    scanf("%s",s+1);n=strlen(s+1);
    fp(i,1,n)a[i]=s[i]-48;
    fp(i,0,9)f[0][0][i][0]=1;
    fp(i,0,n-1)fp(j,0,i)fp(k,1,9)fp(l,0,1)fp(p,0,(l?9:a[i+1]))
         add(f[i+1][j+(p>=k)][k][l|(p<a[i+1])],f[i][j][k][l]);
    fp(k,1,9){
        int tp=1;
        fp(i,1,n)add(ans,1ll*tp*(f[n][i][k][0]+f[n][i][k][1])%P),tp=(10ll*tp+1)%P;
    }printf("%d",ans);
return 0;
}

发现这个转移是可以滚动的,而且每一个kk还可以分开考虑贡献

所以泥萌可以自己再卡一卡常数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值