题意
一个10进制表示的正整数,如果从左到右,每一位的数字都不小于前一位的数字,则被称为上升数。
给出长度N和一个数K,求有多少个长度恰好为N的上升数,是K的倍数。
1<=N<=1018,2<=K<=500
1
<=
N
<=
10
1
8
,
2
<=
K
<=
500
题解
想了很久,一开始想到了一个
O(k2∗94∗logn)
O
(
k
2
∗
9
4
∗
l
o
g
n
)
的做法
然后想尽了了办法优化为了
O(k2∗93∗logn)
O
(
k
2
∗
9
3
∗
l
o
g
n
)
最后在51nodT炸了
然后怎么想都不知道怎么把
93
9
3
降下来
在这里还是写一下吧。。毕竟是自己想出来的,如果
k
k
放小一些可能还可以做不同的情况
我们考虑到n很大,于是我们就考虑二分合并啊
就是,我们要求n的答案,不妨先求n/2的答案,然后两个合起来
怎么合呢?
我的想法是我们要知道前后两端的值,分别记为
a,b
a
,
b
那么新的串的余数就是
a∗10n/2+b
a
∗
10
n
/
2
+
b
然后就可以愉快地转移了
但是我们发现,我们还要符合上升数
于是只好再加多两维,表示这个串的开头和结尾是什么
然后转移就要枚举两个串的三个量来转移
然后就愉快GG了
真的题解
然后就膜了题解
发现,题解很巧妙
题解是把上升数当做性质
而我是当做限制,区别很大
我们考虑是上升数,所以这个数可以表示成不超过9个”1,11,111,1111…111111”的和
比如说
135=111+11+11+1+1
135
=
111
+
11
+
11
+
1
+
1
然后显然地,11111…1111modk是有循环节的
于是我们可以把余数一样的分成一组
然后就可以DP了
f[u][i][j]表示用到了余数为u的这一组,当前用了i个数了,模数是多少
f
[
u
]
[
i
]
[
j
]
表
示
用
到
了
余
数
为
u
的
这
一
组
,
当
前
用
了
i
个
数
了
,
模
数
是
多
少
然后胡乱转移就可以了
要学习的是处理上升数的这种方式,把限制变为性质就可以使复杂度降下来了
时间复杂度是
O(k2∗92)
O
(
k
2
∗
9
2
)
的,并且和n都没有关系了
CODE:
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
const LL MOD=999911659;
const LL K=505;
LL n,k;
LL cnt[K];//有多少种
LL g[K];
LL now=0;
LL f[K][10][K];//现在用到第几个余数 已经用了多少个了 n个1不算 余数是多少
LL inv[10];
void add (LL &x,LL y) {x=(x+y)%MOD;}
int main()
{
scanf("%lld%lld",&n,&k);
cnt[0]++;
if (n<=k)
{
for (LL u=1;u<=n;u++) {now=(now*10+1)%k;cnt[now]++;}
}
else
{
LL lalal,len;
for (LL u=1;u<=k+1;u++)
{
now=(now*10+1)%k;
if (cnt[now]!=0) {lalal=g[now];len=u-g[now];break;}
cnt[now]++;
g[now]=u;
}
//printf("%lld %lld\n",lalal,len);
for (LL u=0;u<k;u++)
{
if (cnt[u]>0&&g[u]>=lalal)//这个会出现在循环里面
{
cnt[u]=(n-lalal+1)/len;
if ((g[u]-lalal+1)<=(n-lalal+1)%len)//不完整的循环
cnt[u]++;
if ((g[u]-lalal+1)%len==(n-lalal+1)%len)
now=u;//最后一个一定要用
}
}
}
inv[1]=1;
for (LL u=2;u<=9;u++) inv[u]=MOD-(MOD/u)*inv[MOD%u]%MOD;
f[0][1][now]=1;
for (LL u=0;u<k;u++)
for (LL i=1;i<=9;i++)//转移
for (LL j=0;j<k;j++)
{
LL lalal=1;
//C(cnt+l-1,l);
for (LL l=0;l<=i;l++) //用多少个
{
add(f[u+1][i][j],f[u][i-l][(j-l*u%k+k)%k]*lalal%MOD);
lalal=lalal*((cnt[u]+l)%MOD)%MOD*inv[l+1]%MOD;
}
}
printf("%lld\n",f[k][9][0]);
return 0;
}