CF908G New Year and Original Order
前言:
一道2800的数位dp,说实话做的时候还是挺虚的,但是做着做着就过了?果然数位dp的rating都是虚高
大意:
求区间[L,R]内每一个数字在各数位排序后得到的数的和,答案对1e9+7取模
n<=1e700
思路:
首先这种题目是存在一定套路的,但是没见过基本是想不出来的(估计也是其rating高的原因)
这道题唯一的性质就是每一个数字的各个数位是升序排列的,然后我们可以发现这么一个结论:
每一个数字都可以被最多9个由1组成的数累加得到
举个例子,23349,可以按照如下方式拆分
最多只有9个是因为每一位最大只有9。然后我们看一下每一行的1的个数,其实也不难发现,第i行的1的个数=大于等于i的数字的个数,手模一下就可以验证了。
所以在第d行,计数字x中>=d的数字的个数为k,则x在第d行的贡献就是(10^k-1)/9(其实就是在构造k个1)
在此基础上,我们分9次来计算每一行的贡献。对于第d行,设dpi,j表示前i位,有j位>=d,直接做数位dp即可
所以此题的关键就是用1来组成数字,剩余的工作就都是很基础的了。不好想,只能说留个印象。
code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define endl '\n'
const ll N=1e5+10;
const ll mod=1e9+7;
string n;
ll a[710];
ll cnt=0;
ll dp[710][710];
ll p[710];
ll inv9;
ll ksm(ll x,ll y)
{
ll ans=1;
while(y)
{
if(y&1) ans=ans*x%mod;
x=x*x%mod;
y>>=1;
}
return ans;
}
ll inv(ll x)
{
return ksm(x,mod-2);
}
void init()
{
inv9=inv(9);
ll now=1;
for(int i=1;i<=710;++i)
{
p[i]=((now*10%mod)-1+mod)%mod*inv9%mod;
now=now*10%mod;
}
// for(int i=1;i<=10;++i) cout<<p[i]<<endl;
}
ll dfs(ll x,ll op,ll pre,ll d)
{
if(!x) return p[pre];
if(!op&&dp[x][pre]!=-1) return dp[x][pre];
ll tot=0;
ll lim=op?a[x]:9;
for(int i=0;i<=lim;++i)
{
if(i<d) tot=(tot+dfs(x-1,op&&i==lim,pre,d))%mod;
else tot=(tot+dfs(x-1,op&&i==lim,pre+1,d))%mod;
}
if(!op) dp[x][pre]=tot;
return tot;
}
ll f(string s)
{
cnt=0;
int len=s.size();
for(int i=len-1;i>=0;--i)
{
a[++cnt]=s[i]-'0';
}
ll ans=0;
for(int i=1;i<=9;++i)
{
memset(dp,-1,sizeof dp);
ans=(ans+dfs(cnt,1,0,i))%mod;
}
return ans;
}
void solve()
{
init();
cin>>n;
cout<<f(n)<<endl;
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
solve();
return 0;
}