H y p e r l i n k Hyperlink Hyperlink
http://poj.org/problem?id=3208
D e s c r i p t i o n Description Description
定义包含三个连续的6的数为魔鬼数
求第
n
n
n小的魔鬼数
数据范围: n ≤ 5 × 1 0 9 n\leq 5\times 10^9 n≤5×109
S o l u t i o n Solution Solution
设 f [ i ] [ j ] f[i][j] f[i][j]表示长度为 i i i,前 j j j位都是6的魔鬼数个数(当 j > 3 j>3 j>3时,默认存到 j = 3 j=3 j=3处),该 d p dp dp允许前导0的存在
对于
f
[
i
]
[
0
]
f[i][0]
f[i][0],只要这一位填的不是6,都可以转移过来,即
f
[
i
]
[
0
]
=
9
×
(
f
[
i
−
1
]
[
0
]
+
f
[
i
−
1
]
[
1
]
+
f
[
i
−
1
]
[
2
]
)
f[i][0]=9\times (f[i-1][0]+f[i-1][1]+f[i-1][2])
f[i][0]=9×(f[i−1][0]+f[i−1][1]+f[i−1][2])
对于
f
[
i
]
[
1
]
f[i][1]
f[i][1],这一位只能填6,即
f
[
i
]
[
1
]
=
f
[
i
−
1
]
[
0
]
f[i][1]=f[i-1][0]
f[i][1]=f[i−1][0]
显然
f
[
i
]
[
2
]
=
f
[
i
−
1
]
[
1
]
f[i][2]=f[i-1][1]
f[i][2]=f[i−1][1],
f
[
i
]
[
3
]
=
f
[
i
−
1
]
[
2
]
f[i][3]=f[i-1][2]
f[i][3]=f[i−1][2]
但是,如果上一位前已经满足是魔鬼数,则这一位可以随便填,即
f
[
i
]
[
3
]
+
=
f
[
i
−
1
]
[
3
]
×
10
f[i][3]+=f[i-1][3]\times 10
f[i][3]+=f[i−1][3]×10
接下来用试填法或二分答案转判定都可
C o d e Code Code
#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;int T,k;
LL n,f[25][4],m,cnt;
inline LL read()
{
char c;LL d=1,f=0;
while(c=getchar(),!isdigit(c)) if(c=='-') d=-1;f=(f<<3)+(f<<1)+c-48;
while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
return d*f;
}
signed main()
{
f[0][0]=1;
for(register int i=0;i<21;i++)
{
for(register int j=0;j<3;j++)
{
f[i+1][j+1]+=f[i][j];
f[i+1][0]+=f[i][j]*9;
}
f[i+1][3]+=f[i][3]*10;
}
T=read();
while(T--)
{
n=read();m=3;
while(f[m][3]<n) m++;k=0;//计算答案的位数,k表示目前已经填了几个连续的6
for(register int i=m;i;i--)
{
for(register int j=0;j<=9;j++)//考虑j填0~9,第i位的填法
{
cnt=f[i-1][3];
if(j==6||k==3)
//如果这一位填的是6,如果是第一个,则后面要加上f[i-1][2]
//如果是第二个6,则后面还要加上f[i-1][1]
//如果是第三个6,则后面还要加上f[i-1][0]
for(register int l=max(3-k-(j==6),0);l<3;l++)
cnt+=f[i-1][l];
if(cnt<n) n-=cnt;
else
{
putchar(j+48);
if(k<3)
{
if(j==6) k++;
else k=0;
}
break;
}
}
}
putchar(10);
}
}