POJ3208 Apocalypse Someday Solution

来源:互联网 发布:mysql查看所有表空间 编辑:程序博客网 时间:2024/05/29 16:07

题目大意:

求出第k小的包含有'666'的正整数。k<=5*10^7.


Sol:

将问题转化在KMP自动机上。

存在四个节点,0,1,2,3,分别表示当前前缀匹配6的个数为0,1,2,3。其中3是接受状态。

注意从3出发的任何转移均回到自身。此外0.go[6]=1,1.go[6]=2,2.go[6]=3.此外的转移均为0.

随后,我们预处理出从状态i出发走j步到达接受状态的个数num[i][j]。

而且,容易算出长度为i的能够到达接受状态的总数tot[i]。


对于每一个n,我们首先确定这是几位数。

随后从高到低对位进行枚举。如何确定这一位的数字是多少呢?

从小到大进行尝试,设当前状态为p,枚举数字为j,当前位数为i,要求剩下的第k大,那么:

若num[p.go[j]][i]>=k,证明这一位为j,p=p.go[j];否则k-=num[p.go[j]][i].

分析算法的时间复杂度:预处理O(logn),对于每组询问O(log^2n),轻松水过。


Code:

#include <cstdio>#include <cstring>#include <cctype>#include <algorithm>#include <iostream>using namespace std;typedef long long LL;int go[4][10];#define Len 10LL tmp[Len + 1][4], sav[4][Len + 1], tot[Len + 1];const int ch[] = {0, 0, 0, 0, 0, 0, 1, 0, 0, 0};inline void pre() {register int i, j, k, p;go[0][6] = 1, go[1][6] = 2, go[2][6] = 3;for(i = 0; i < 10; ++i)go[3][i] = 3;for(i = 0; i < 4; ++i) {memset(tmp, 0, sizeof(tmp));tmp[0][i] = 1;for(j = 0; j < Len; ++j)for(k = 0; k < 4; ++k)for(p = 0; p < 10; ++p)tmp[j + 1][go[k][p]] += tmp[j][k];for(int len = 1; len <= Len; ++len)sav[i][len] = tmp[len - 1][3];}for(i = 1; i <= Len; ++i)for(j = 1; j < 10; ++j)tot[i] += sav[ch[j]][i];}int main() {pre();register int i, j;int n, T, len;scanf("%d", &T);while(T--) {int n;scanf("%d", &n);for(len = 3; len <= Len; ++len) {if (tot[len] >= n)break;n -= tot[len];}int now = 0;for(i = len; i >= 1; --i) {int lim = (i == len);for(j = lim; j < 10; ++j) {if (n <= sav[go[now][j]][i]) {now = go[now][j];printf("%d", j);break;}n -= sav[go[now][j]][i];}}puts("");}return 0;}


0 0
原创粉丝点击