【数论】二段数

【数论】二段数

#include <iostream>
using namespace std;
// 思路一: 判断n的倍数是不是二段数。
// 已知二段数由两种数字组成,利用枚举。我们把该数每一位的数字用桶排序排到数组里,如果数组的和为2,那么该数为二段数,进行输出即可。 如果不是,进行翻倍后再次判断
// 缺点:运行时间太长

// 思路二
const int maxn = 10010; // 表示最大的位数长度,定义为const,不可变的固定大小

int a[maxn], b[maxn];
// m, s, n = total - m 可得,不用假设。 t
int m, total, s, t, aptotal, apm, aps, apt, k; // 先声明要用到的变量,记录符合要求的最小二段数
int n;                                         // n为输入的数,求既是二段数,又是n的倍数
bool ck()                                      // 返回为bool类型的函数,检测二段数是否大于n
{
    int p, r;
    if (total > 5) // 剪枝   题目已知n的最大值是99999,是5位数,total大于5的话,该二段数必定大于n。直接返回true即可
        return true;
    p = s; // 4
    r = t; // 1
    //m = 0时,s只有一位,因此 m 的初值不能和 total - m一样为 1
    for (int q = 0; q < m; q++)
    {
        p = p * 10 + s; // 444444
    }

    for (int q = 0; q < total - m; q++)
        p = p * 10; // 44444400
    for (int q = 1; q < total - m; q++)
        r = r * 10 + t; // 11
    return p + r > n;   // p + r即为二段数本身的数值大小
}

int main()
{
    while (cin >> n, n) //?逗号表达式?  输入0退出循环
    {
        printf("%d: ", n);
        if (n == 1)
        { // 剪枝
            puts("10");
            continue;
        }

        a[0] = 1;
        b[0] = 1;
        // 数列1:a[1]=1,a[i+1]=(a[i]*10+1) mod N,i = 1,2,3,… a[i]就是连续i个1除以N的余数。
        // 数列2:b[0]=1,b[i+1]=b[i]*10 mod N,i = 0,1,2,3,… b[i]就是10的i次方除以N的余数。
        for (int i = 1; i < 9999; i++) // 初始化a[i]、b[i]两个数组

            a[i] = (a[i - 1] * 10 + 1) % n; // a[1] = 11 % n   a[2] = a[1] % n ...最后存入数组的是一串重复循环的数字。当n=30时,数组内容是1, 11, 21循环
        for (int i = 1; i < 999; i++)
            b[i] = b[i - 1] * 10 % n; // b[1] = 10 % n  b[2] = 100 % n

        // 接下来只要枚举m,s,n,t就可以了。按照(m+n)的值从小到大枚举,(m+n)确定后枚举m,则n可以直接计算出来,不需要枚举。m和n确定之后枚举s和t。一旦找到解,后面的(m+n)值就不需要继续枚举下去了。
        for (total = 1, aps = 0; total < 9999; total++)
        {
            k = 0;
            if ((n % 10 == 0 || n % 25 == 0) && total > 11) // 剪枝 当total == 12时,仍然未求出 此时,m从 2位开始 n 为11,即1100000000000
            {
                k = total - 11;
            }
            for (m = k; m < total; m++)
            {
                for (s = 1; s < 10; s++)
                {
                    for (t = 0; t < (n % 10 ? 10 : 1); t++) // 剪枝:n是10的倍数,t只能取0
                    {
                        // t != s 确保是二段数
                        // a[m] * b [n] * s + a[n] * t即为此时的二段数  判断是否整除n,整除n就是n的倍数
                        // ck()判断二段数是否大于n
                        //!aps || s < aps   !aps确保第一次满足条件的二段数保存一次,s<aps确保 在total不变的情况下,二段数不会随便被更新.
                        // 只有当s更小时,即有另一个比当前保存的二段数更小时,才会更新  因为循环先加的是 total-n 的值。 例如 n = 52时, 二段数先是988,之后m+1后,更新为884
                        if (t != s && (((long long)a[m]) * b[total - m] * s + a[total - m - 1] * t) % n == 0 && ck() && (!aps || s < aps))
                        {
                            aptotal = total;
                            apm = m;
                            aps = s;
                            apt = t;
                        }
                    }
                }
            }

            if (aps)
                break;
        }
        for (int x = 0; x < apm + 1; x++) // 打印输出二段数
        {
            cout << aps;
        }

        for (int x = 0; x < aptotal - apm; x++)
        {
            cout << apt;
        }

        cout << endl;
    }
}
posted @ 2023-03-06 20:49  destinyCosette  阅读(64)  评论(0)    收藏  举报