AcWing 4404. X 进制减法 --- 关于每一位权重计算的详细解释

题目

进制规定了数字在数位上逢几进一。

\(X\) 进制是一种很神奇的进制,因为其每一数位的进制并不固定!

例如说某种 \(X\) 进制数,最低数位为二进制,第二数位为十进制,第三数位为八进制,则 \(X\) 进制数 \(321\) 转换为十进制数为 \(65\)

现在有两个 \(X\) 进制表示的整数 \(A\)\(B\),但是其具体每一数位的进制还不确定,只知道 \(A\)\(B\) 是同一进制规则,且每一数位最高为 \(N\) 进制,最低为二进制。

请你算出 \(A − B\) 的结果最小可能是多少。

请注意,你需要保证 \(A\)\(B\)\(X\) 进制下都是合法的,即每一数位上的数字要小于其进制。

输入格式

第一行一个正整数 \(N\),含义如题面所述。

第二行一个正整数 \(M_a\),表示 \(X\) 进制数 \(A\) 的位数。

第三行 \(M_a\) 个用空格分开的整数,表示 \(X\) 进制数 \(A\) 按从高位到低位顺序各个数位上的数字在十进制下的表示。

第四行一个正整数 \(M_b\),表示 \(X\) 进制数 \(B\) 的位数。

第五行 \(M_b\) 个用空格分开的整数,表示 \(X\) 进制数 \(B\) 按从高位到低位顺序各个数位上的数字在十进制下的表示。

请注意,输入中的所有数字都是十进制的。

输出格式

输出一行一个整数,表示 \(X\) 进制数 \(A − B\) 的结果的最小可能值转换为十进制后再模 \(1000000007\) 的结果。

数据范围

对于 \(30\%\) 的数据,\(N ≤ 10; M_a, M_b ≤ 8\)
对于 \(100\%\) 的数据,\(2 ≤ N ≤ 1000; 1 ≤ M_a, M_b ≤ 100000; A ≥ B\)

输入样例:

11
3
10 4 0
3
1 2 0

输出样例:

94

样例解释

当进制为:最低位 \(2\) 进制,第二数位 \(5\) 进制,第三数位 \(11\) 进制时,减法得到的差最小。

此时 \(A\) 在十进制下是 \(108\)\(B\) 在十进制下是 \(14\),差值是 \(94\)

题解

题意费了好大劲才搞明白 这里x进制的计算方法不像传统那样 第几位乘以对应进制的几次方
也就是说传统的某一位n的权重是其进制\(X^n\) 例如八进制第3位的权重是\(8^2\)
但本题不同 X进制某一位的权重是其后面几位进制的乘积
因为每一位的进制不同 那进到当前位的条件就不同
进位进到当前位的条件取决于低位的进制 而低位的进位取决于更低位的进制 这么讲非常抽象
例如321 第三位的权重就取决于其后面两位的进制 因为你要想进到第三位 你就必须要通过第1位的考验和第2位的考验 最终才能进到第3位
举个例子这和你闯关打boss一样 你想打到第三关 那么你就要通过第一关和第二关
假设第一关进制为\(j\) 第二关进制为\(i\)
通过第一关的条件就是你比他的进制j大 这样你可以顺利进位到第二关
通过第二关的条件就是你比第二关的进制i大 这样你可以顺利进位到第三关
由于是不断递进的关系 所以我们第三关的权重就是 \(i*j\)


这个问题解决后 我们来处理怎么才能得到相减后的最小值
因为题目要求两个数的每一位的进制都是相同的 现在每一位上的数字都是已知的
那我们只需要让每一位的进制在符合条件的情况下最小 就能让A和B尽可能的小 他们的差也就会更小
那什么是符合条件的进制呢?
就是当前位上的数字小于当前位的进制 例如当前位数字是8 我们最小可以取9进制 程序中利用max()实现

本题需要注意的点就是数据范围很大 每一个运算语句最好都加上取余mod

#include <bits/stdc++.h>

using namespace std;

const int N = 1010, M = 1e5 + 10, mod = 1000000007;

typedef long long LL;

int a[M], b[M], w[M]; //a[]存储A各位的数字,b[]存储B各位的数字,w[]存储A和B各位的进制
int n, ma, mb;
LL mul[M]; //存储各位的实际权重 由于权重是当前位后面所有进制之积 M上限1e5 N上限1000 可能会溢出我们开longlong
LL A, B; //存一下数A和B的实际大小 以十进制表示

int main()
{
    scanf("%d", &n);
    scanf("%d", &ma);
    for (int i = ma; i > 0; i -- ) scanf("%d", &a[i]); //倒序存储 最高位是第ma位
    scanf("%d", &mb);
    for (int i = mb; i > 0; i -- ) scanf("%d", &b[i]);

    //确定各位的最小进制 进制最小是A和B当前位上的最大值+1
    int mm = max(ma, mb); //先找到A和B中位数更大的那个数 的 最高位数
    for (int i = 1; i <= mm; i ++ )
    {
        int tmax = max(a[i], b[i]);
        w[i] = max(2, tmax + 1);  //题目要求的最小进制是二进制 选出满足条件的当前位最小进制
    }

    //确定各位的权重 第i位的权重就是 第i-1位的权重*第i-1位的进制
    mul[1] = 1; //最低位的权重是1
    for (int i = 2; i <= mm; i ++ ) //从倒数第二位开始确定权重
    {
        mul[i] = (w[i - 1] * mul[i - 1]) % mod; //当前位的权重等于上一位的进制*上一位的权重 这也是i从小到大循环的原因 确保计算本位时 上一位的权重已经算过了 数据很大 取余mod
    }

    //分别计算A和B 第i位的值和第i位的权重相乘 最后累加 就能得到 A和B的十进制表示
    for (int i = 1; i <= ma; i ++ )
    {
        A = (A + a[i] * mul[i]) % mod; //每一步都取余mod防止溢出
    }
    for (int i = 1; i <= mb; i ++ )
    {
        B = (B + b[i] * mul[i]) % mod;
    }

    printf("%lld\n", (A - B + mod) % mod); //这里在A-B后一定要+mod 否则可能本来A比B大 但由于多次取模后 A比B小了导致运算出现负值 所以我们在运算内再加上mod

    return 0;
}

小坑

for (int i = 1; i <= mb; i ++ )
    {
        B = (B + b[i] * mul[i]) % mod;
    }

不要写成B += (b[i] * mul[i]) % mod; 这样只能每次取余b[i] * mul[i]的运算结果
但B还是会溢出

posted @ 2024-04-16 16:56  MsEEi  阅读(2)  评论(0编辑  收藏  举报