GDOI2012 数字统计(digit) 简单的公式推导(高精度模板题)

题目大意

给你10个数 ai ,表示 1 到某个数 N 中出现的 0 9 的个数,现在要求输出这个数 N ,如果无解则输出 1

ai10512 保证输入的是非负整数并且至少有一个为正整数。

解体思路

一点特别关键的是,题目给了我们 1 ~ N 0 ~ 9 出现的次数,那么就相当于告诉了我们 1 ~ N 所有数的位数和。而我们又不难发现 1 ~ 9 的贡献是 19 10 ~ 99 的贡献是 290 100 ~ 999 的贡献是 3900 …… 显然 i 位数的贡献是 i910i1

有了这个我们可以依次判断 N 是不是 i 位数。简单来说就是把 i 从1枚举,如果当前的位数和比第 i 位数的贡献大,就用把总位数减去这个贡献, i 加一。否则我们要找的 N 就是 i 位数。知道位数后,就可以很简单的求出 N 的具体数字了,就是直接用剩下的位数除以 i ,假设商为 j ,如果除不尽就肯定无解,处的尽就是 i 位数的第 j 个数。

但是,这样判断不一定正确,应为我们只是用总位数去判断,并没有单独考虑某一个数字是否合法,有可能一个数字多了一,一个少了一,我们依旧会得出答案。这种问题也很好解决,我们只需算出 1 ~ N 1 ~ 9 出现的次数,判断一下跟读入进来的一不一样就可以了。

判断时我们只需把 N 从地位到高位枚举,来更新答案,假设现在做到第 i 位,我们要求 N 的后 i 有多少个 1 (其他数字依此类推)。设我们要求的是 Fi ,那么 Fi=(Ni1)Gi+Fi1+Pi
Hi :表示 N i 位组成的数。
Ni :表示 N 的第 i 位是什么数字。
Gi :表示 10i 内某一个数字出现的次数( 1 ~ 9 出现次数一样)。
Pi :分三种情况:如果 Ni 大于 1 Pi=10i1 ;如果 Ni 等于 1 Pi=Hi1+1 ;如果 Ni 小于 1 Pi=0

其中 Ni1Gi 表示 1 ~ (Ni1)10i1 中前 i1 位中 1 的个数,而 Fi1 表示 (Ni1)10i1 ~ Hi 中前 i1 位中 1 的次数。 Pi 表示 1 ~ Hi 中第 i 位中 1 的个数。推理过程比较简单就不细说了。

最后看到输入的 ai 那么大,那就要做好套高精度的准备。
由于除了高精度外实现比较简单,所以是一个高精度的模板题!

程序

高精度模板(加,减,乘,除,比较)

//GDOI2012 digit YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;

const int MAXN = 600;

struct Node {int Num[MAXN], Bit;};

char S[MAXN];
Node All, Ans, Ten, One, Dig[10], Real[10];

int Judge(Node A, Node B) {
    if (A.Bit > B.Bit) return 1;
    if (A.Bit < B.Bit) return 0;
    for (int i = A.Bit; i; i --) {
        if (A.Num[i] > B.Num[i]) return 1;
        if (A.Num[i] < B.Num[i]) return 0;
    }
    return 2;
}

Node Dec(Node A, Node B) {
    Node C;
    memset(C.Num, 0, sizeof C.Num);
    for (int i = 1; i <= A.Bit; i ++) {
        C.Num[i] = C.Num[i] + A.Num[i] - B.Num[i];
        if (C.Num[i] < 0) C.Num[i] += 10, C.Num[i + 1] --;
    }
    C.Bit = A.Bit;
    while (!C.Num[C.Bit]) C.Bit --;
    return C;
}

Node Add(Node A, Node B) {
    Node C;
    memset(C.Num, 0, sizeof C.Num);
    C.Bit = max(A.Bit, B.Bit);
    for (int i = 1; i <= C.Bit; i ++) {
        C.Num[i] = C.Num[i] + A.Num[i] + B.Num[i];
        C.Num[i + 1] = C.Num[i] / 10;
        C.Num[i] = C.Num[i] % 10;
    }
    if (C.Num[C.Bit + 1]) C.Bit ++;
    return C;
}

Node Mul(Node A, Node B) {
    Node C;
    memset(C.Num, 0, sizeof C.Num);
    for (int i = 1; i <= A.Bit; i ++) 
        for (int j = 1; j <= B.Bit; j ++) {
            C.Num[i + j - 1] = C.Num[i + j - 1] + (A.Num[i] * B.Num[j]);
            C.Num[i + j] = C.Num[i + j] + (C.Num[i + j - 1] / 10);
            C.Num[i + j - 1] = C.Num[i + j - 1] % 10;
        }
    C.Bit = A.Bit + B.Bit - 1;
    if (C.Num[C.Bit + 1]) C.Bit ++;
    return C;
}

Node Div(Node A, Node B) {
    Node C, Tmp;
    memset(C.Num, 0, sizeof C.Num);
    memset(Tmp.Num, 0, sizeof Tmp.Num);
    Tmp.Bit = A.Bit - B.Bit + 1, Tmp.Num[Tmp.Bit] = 1;
    C.Bit = 0;
    for (; Tmp.Bit; ) {
        Node D = Mul(B, Tmp);
        while (Judge(A, D)) {
            A = Dec(A, D);
            C = Add(C, Tmp);
        }
        Tmp.Num[Tmp.Bit] = 0;
        Tmp.Num[-- Tmp.Bit] = 1;
    }
    return C;
}

Node Check_Bit() {
    Node Tmp, Bit; 
    memset(Bit.Num, 0, sizeof Bit.Num), memset(Tmp.Num, 0, sizeof Tmp.Num);
    Tmp.Bit = 1, Tmp.Num[1] = 9;
    Bit.Bit = 0, Bit.Num[1] = 0;

    while (All.Bit > 0) {
        Bit = Add(Bit, One);
        Node Check = Mul(Tmp, Bit);
        if (!Judge(All, Check)) break;
        All = Dec(All, Check);
        Tmp = Mul(Tmp, Ten);
    }
    return Bit;
}

int TransInt(Node Bit) {
    int Ret = 0;
    for (int i = Bit.Bit; i; i --) Ret = Ret * 10 + Bit.Num[i];
    return Ret;
}

void GetAns() {
    for (int i = 0; i < 10; i ++) All = Add(All, Dig[i]);
    Node Bit = Check_Bit();
    Node Inc = Div(All, Bit);
    int bit = TransInt(Bit);
    Ans = One;
    for (int i = 1; i < bit; i ++) Ans = Mul(Ans, Ten);
    if (Inc.Bit != 0) Ans = Add(Ans, Dec(Inc, One));
}

bool CheckAns() {
    Node Tmp, Cnt, S, All;
    memset(Tmp.Num, 0, sizeof Tmp.Num), memset(All.Num, 0, sizeof All.Num);
    memset(Cnt.Num, 0, sizeof Cnt.Num), memset(S.Num, 0, sizeof S.Num);

    All.Bit = 1, All.Num[1] = 1;
    Tmp.Bit = 1, Tmp.Num[1] = 1;
    Cnt.Bit = 1, Cnt.Num[1] = 1;
    S.Bit = 0;

    for (int i = 1; i <= Ans.Num[1]; i ++) Real[i].Bit = 1, Real[i].Num[1] = 1;
    for (int i = 2; i <= Ans.Bit; i ++) {
        Cnt = Mul(Cnt, Ten);
        S.Num[++ S.Bit] = Ans.Num[i - 1];
        Node T;
        memset(T.Num, 0, sizeof T.Num);
        T.Bit = 1; T.Num[1] = Ans.Num[i];
        for (int j = 1; j < 10; j ++) {
            Real[j] = Add(Real[j], Mul(T, All));
            if (j < Ans.Num[i]) Real[j] = Add(Cnt, Real[j]);
            if (j == Ans.Num[i]) Real[j] = Add(Add(S, One), Real[j]);
        }
        Tmp = Add(Cnt, Mul(All, Dec(Ten, One)));
        All = Add(All, Tmp);
    }
    for (int i = 1; i < 10; i ++)
        if (Judge(Dig[i], Real[i]) < 2) return 0;
    return 1;
}

int main() {
    One.Bit = 1, One.Num[1] = 1;
    Ten.Bit = 2, Ten.Num[1] = 0, Ten.Num[2] = 1;

    for (int i = 0; i < 10; i ++) {
        scanf("%s", S + 1);
        int Len = strlen(S + 1);
        for (int j = 1; j <= Len; j ++) Dig[i].Num[j] = S[Len - j + 1] - '0';
        Dig[i].Bit = Len;
    }

    GetAns();
    bool Check = CheckAns();
    if (!Check) {
        printf("-1\n");
        return 0;
    }
    for (int i = Ans.Bit; i; i --) printf("%d", Ans.Num[i]);
    printf("\n");
}

你可能感兴趣的:(GDOI2012 数字统计(digit) 简单的公式推导(高精度模板题))