找出乘积的每个数字与乘数不重复

早上起来看瞎逛,看了个题目,

找出这样的数:
    1. 6位正整数
    2. 每个数位上的数字不同
    3. 其平方数的每个数位不含原数字的任何组成数位

如: 203879 * 203879 = 41566646641

    203879 是个6位数,并且它的每个数位上的数字都是不同的,并且它平方后的所有数位上都不出现组成它自身的数字。

简单的解法(其实有优化的方法,但不适合编程)

#include <iostream>
#include <vector>
#include <algorithm>
void num2vector(long long num, std::vector<int> &vNum)
{
    while (num > 0)
    {
        vNum.push_back(num % 10);
        num /= 10;
    }
}

int main(int argc, char *argv[])
{
    long long a = 0;
    long long b = 0;
    std::vector<int> vA, vB, vC;
    std::vector<int>::iterator vit;
    
    for (a = 100000; a < 1000000; a++)
    {
        if (vA.size() > 0) vA.clear();
        if (vB.size() > 0) vB.clear();
        if (vC.size() > 0) vC.clear();
        
        num2vector(a, vA);
        sort(vA.begin(), vA.end());
        vit = std::unique(vA.begin(), vA.end());
        if (vit != vA.end())
            continue;
        b = a * a;
        num2vector(b, vB);
        sort(vB.begin(), vB.end());
        vit = unique(vB.begin(), vB.end());
        vB.erase(vit, vB.end());
        std::set_intersection(vA.begin(), vA.end(), vB.begin(), vB.end(),
                              std::inserter(vC, vC.begin()));
        if (vC.size() == 0)
        {
            std::cout<<a<<" * "<<a<<" = "<<b<<std::endl;
        }
    }
    return 0;
}

结果: 639172 * 639172 = 408540845584

<think>好的,我现在需要解决这个问题:给定一个排列,计算它的字典序值,并找到下一个排列。这个问题分为两个部分,我需要分别处理。 首先,理解字典序值的计算。例如,当n=3时,排列123的序号是0,132是1,依此类推。计算字典序值的方法通常是基于每个位置上的元素在当前剩余元素中的排名,然后乘以相应的阶乘。例如,对于排列中的每个元素,确定有多少个比它小的元素还未被使用,然后将这些数目乘以对应的阶乘数,累加得到总序号。 接着是下一个排列的问题。下一个排列指的是在字典序中当前排列的下一个更大的排列。如果当前排列已经是最大的(如321),则下一个排列是最小的(如123)。通常的方法是: 1. 从右往左找到第一个比右边元素小的位置i。 2. 在i的右边找到比arr[i]大的最小元素,交换它们。 3. 将i右边的元素逆序,使其成为最小序列。 现在需要将这两个步骤用代码实现。首先处理输入,读取n和排列数组。然后分别处理两个部分。 对于字典序值的计算,可能需要一个数组记录每个元素后面比它小的元素的个数,然后乘以对应的阶乘。需要注意的是阶乘可能会很大,特别是当n较大时,所以要用长整型或者大数处理。但题目中给出的示例输入是n=8,输出是8227,看起来在长整型范围内,因此暂时需要处理大数。 例如,对于排列2 6 4 5 8 1 7 3: 第一位是2,在剩下的元素中比2小的元素有1个(即1),所以贡献1*7!。然后剩下的元素是1,3,4,5,6,7,8,但需要确认是否正确? 或者,应该考虑每个位置上的元素在剩余未使用的元素中的排名,减去1,然后乘以剩余位置的阶乘。例如,第一个元素是2,剩下的元素中比2小的有1个(即1),所以贡献1 * 7!。然后第二个元素是6,剩下的元素是1,3,4,5,7,8,比6小的有4个(1,3,4,5),所以贡献4 * 6!。依此类推,直到所有元素处理完毕。 这部分的阶乘需要预先计算好,比如从0!到n!的值存储在一个数组中。 然后是下一个排列的计算。按照算法步骤: 1. 找到i,使得从右到左第一个满足arr[i] < arr[i+1]的位置。例如,排列2 6 4 5 8 1 7 3,从右往左找,7和3是降序,1和7是升序?或者需要更仔细地检查。比如,原排列是2 6 4 5 8 1 7 3。从右往左,i=5的位置是1(索引从0开始的话),因为arr[5]=1,右边有7和3都比它大?或者可能需要重新计算索引。例如,原排列是索引0到7: 索引0:2, 1:6, 2:4, 3:5, 4:8, 5:1, 6:7, 7:3。 从右往左找i,使得arr[i] < arr[i+1]。这里,比较i=6和i=7:7>3,符合。i=5是1,i=6是7,满足1<7,所以i=5。此时需要在i的右边(即从i+1到末尾)找到比arr[i]=1大的最小元素。右边元素是7和3,比1大的最小是3。交换1和3的位置,得到排列变成2 6 4 5 8 3 7 1。然后将i=5之后的元素(即索引6和7)逆序,得到7 1变成1 7。所以整个下一个排列是2 6 4 5 8 3 1 7。这示例的输出一致。 所以,代码需要正确实现这两个步骤。 现在需要考虑如何编写代码。对于字典序值的计算,步骤如下: 初始化阶乘数组fact,其中fact[i] = i!。例如,fact[0]=1,fact[1]=1,fact[2]=2,等等。 然后,对于每个元素的位置i(从0到n-1): - 统计当前元素arr[i]在剩余未使用的元素中有多少个比它小的元素。这些元素的个数乘以fact[n-1 -i],并将结果累加到序号中。 - 将arr[i]标记为已使用,或者维护一个已使用的元素的列表。为了高效地统计比当前元素小的未使用的数目,可以使用一个有序的数据结构,如树状数组或者有序列表。例如,维护一个数组used,初始为全false。对于每个位置i,遍历元素arr[i],统计所有未被使用的小于arr[i]的元素的数量。这可能需要O(n^2)的时间,对于n<=20来说是可以接受的。 或者,可以将排列中的元素按顺序处理,每次记录哪些元素已经被使用。例如,对于当前处理的元素x=arr[i],在剩下的未使用的元素中,比x小的数目等于x-1(因为元素是1到n的排列)减去已经被使用的比x小的元素的数目。例如,初始时,未使用的元素是1到n。当处理第一个元素x时,比x小的数目是x-1。但之后每处理一个元素y,如果y<x,则剩下的数目会减少。所以这可能需要动态计算已经被使用的比x小的元素的数量。 或者,可以维护一个已使用的元素的集合,每次处理当前元素x时,统计在x之前未被使用的元素的数目。比如,使用一个布尔数组used,初始为false。对于每个i,从0到n-1: count = 0; for (int j=1; j < x; j++) { if (!used[j]) count++; } 然后,将count乘以fact[n-1 -i],并累加到序号中。然后将x标记为已使用。 这种方法的时间复杂度是O(n^2),对于n=20来说,时间应该是可以接受的。因为n=20时,n²=400次循环,而阶乘可能会很大,但题目中的n可能会很大,所以应该没问题。 因此,代码的大致结构是: 计算阶乘数组fact。 初始化used数组为false。 初始化序号为0。 对于i从0到n-1: x = arr[i] count = 0 for j from 1 to x-1: if not used[j]: count +=1 rank += count * fact[n-1 -i] used[x] = true 这样得到rank就是字典序值。 然后,处理下一个排列的问题。这需要按照算法步骤: 1. 找到最大的i,使得arr[i] < arr[i+1]。如果找到,说明当前排列是最后一个,下一个是第一个排列。 2. 找到最大的j>i,使得arr[j] > arr[i]。 3. 交换arr[i]和arr[j]。 4. 反转i+1到末尾的元素。 实现时需要注意数组的索引是否正确。例如,在代码中数组可能是0-based的。 例如,对于示例输入: 排列是2 6 4 5 8 1 7 3。要找到i,使得arr[i] < arr[i+1]。我们从右往左遍历: i=7: arr[7]=3,没有右边元素,跳过。 i=6:7和3,7>3,满足。 i=5:1和7,满足1<7,所以i=5。 然后找到j>i,使得arr[j]是比arr[i]=1大的最小的元素。右边的元素是7和3。最大的j是7,因为arr[7]=3>1,但比1大的最小元素是3。所以j=7。 交换i和j的位置,得到排列变为:2 6 4 5 8 3 7 1。 然后反转i+1到末尾的部分,即索引6到7的元素7和1,反转后得到1 7。所以整个排列变成2 6 4 5 8 3 1 7,这示例的输出一致。 所以,在代码中,需要正确处理这些步骤。 综上,代码的结构大致如下: 读取n和排列数组。 计算字典序值: 预先计算阶乘数组。 初始化used数组,按顺序处理每个元素,统计比当前元素小且未使用的数目,乘以对应的阶乘,累加得到结果。 计算下一个排列: 寻找i,如果没有则下一个排列是升序排列。 否则,找j,交换,然后反转。 然后输出字典序值和下一个排列。 现在需要考虑数据结构和边界情况。 例如,当排列是最后一个排列(如n=3的321),下一个排列应该是123。 测试用例: 比如输入n=3,排列3 2 1。字典序值应该是5,下一个排列是1 2 3. 所以,代码中处理这种情况需要正确。 在代码实现时,需要注意数组的索引是否正确,比如循环的起始和结束条件。 现在,开始编写代码。 首先是计算字典序值: 阶乘数组的计算: fact[0] = 1; for i in 1..n: fact[i] = fact[i-1] * i; 但需要注意,当n较大时,fact会溢出。例如,当n=20时,20!是很大的,超出了long long的范围(约9e18)。所以当n超过20时,计算结果会溢出。但是根据题目中的示例,可能n会太大,或者题目保证结果在long long范围内。例如,示例中的n=8,8!是40320,而7!是5040,所以每个项的乘积可能会太大。例如,最大的项可能是(n-1)! * (n-1)。例如,当n=20,最大项是19!*19,这会非常大。可能题目中的测试用例允许使用64位整数。或者题目中的n较小,所以会溢出。 因此,在代码中使用long long类型来存储阶乘和序号。 然后,处理每个元素: 例如,在示例输入中,排列是2 6 4 5 8 1 7 3。处理每个元素: i=0,元素是2,此时未使用的元素是1,3,4,5,6,7,8。比2小的有1个,所以贡献1*7! = 5040. i=1,元素是6,已使用的元素是2。剩下的未使用的元素是1,3,4,5,7,8.比6小的有4个(1,3,4,5),贡献4*6! =4*720=2880. 累计5040+2880=7920. i=2,元素是4,未使用的元素是1,3,5,7,8.比4小的有2个(1,3),贡献2*5! =2*120=240. 累计7920+240=8160. i=3,元素是5,未使用的元素是1,3,7,8.比5小的有2个(1,3),贡献2*4! =48. 累计8160+48=8208. i=4,元素是8,未使用的元素是1,3,7.比8小的有3个,贡献3*3! =18. 累计8208+18=8226. i=5,元素是1,未使用的元素是3,7.比1小的没有,所以0*2! =0. 累计8226+0=8226. i=6,元素是7,未使用的元素是3.比7小的有1个(3),贡献1*1! =1. 累计8226+1=8227. i=7,元素是3,没有剩下的阶乘,贡献0. 总结果为8227,示例一致。 这说明算法是正确的。 因此,代码中的字典序计算部分是正确的。 然后是下一个排列的计算: 需要正确找到i和j。例如,在代码中: 从n-2开始向左遍历,找到第一个i,使得arr[i] < arr[i+1]. 例如,对于示例的数组: 索引5是1,arr[5]=1,arr[6]=7>1,所以i=5. 然后找到j在i+1到n-1之间,最大的索引使得arr[j] > arr[i]. 在这个例子中,arr[i]=1,右边的元素是7和3。最大的j是6,因为arr[6]=7>1,但是当交换i和j后,得到元素7,此时右边的元素是3。然后反转i+1到末尾,即索引6到7,所以7和3交换后变为3和7?或者原数组是2 6 4 5 8 1 7 3,交换i=5和j=7的值,得到2 6 4 5 8 3 7 1。然后反转i+1(即索引6到7)的元素7和1,得到1 7。最终排列是2 6 4 5 8 3 1 7。 所以,在代码中,找到j的步骤应该是在i之后的所有元素中找到比arr[i]大的最小的元素中的最大的索引?或者应该找比arr[i]大的最小的元素中的最大的j? 或者,正确的做法是找到j是i之后所有比arr[i]大的元素中的最小的那个,然后交换,然后反转i+1之后的元素。或者,根据算法,应该找到最大的j>i,使得arr[j]>arr[i],因为这样交换后,i之后的元素仍然保持降序,反转后得到最小的递增序列。 或者,正确的步骤是: 找到i之后,最大的j>i,使得arr[j]>arr[i]。这可以确保交换后,i右边的元素是降序的,这样反转后得到最小的下一个排列。 例如,在示例中,i=5,arr[i]=1。右边的元素是7和3。其中比1大的元素是7和3。最大的j是6(索引6)对应的7,或者索引7的3? 哦,原数组的索引5到7的元素是1、7、3。当i=5时,要找j>5,arr[j]>1。符合条件的j=6和7。在这些j中,要找到最大的j吗?或者要找到比arr[i]大的最小的元素? 或者,正确的做法是:在i的右侧找到最小的比arr[i]大的元素。例如,在示例中,比1大的最小元素是3,所以j=7。交换后,arr[i]变成3,而剩下的元素7和1,然后反转得到1 7,这样得到的排列就是正确的。 所以,正确的步骤是,在i的右边找到比arr[i]大的最小的元素,然后交换。这样,剩下的元素可以按降序排列,反转后得到升序,即最小可能。 所以,在代码中,步骤应为: 找到i之后,从末尾向前找第一个比arr[i]大的元素,这样得到最小的较大元素。 例如,在示例中,i的右边是7和3。从后往前找,第一个比1大的是3,所以j=7。交换之后得到3和1,剩下的元素是7和1。反转这部分得到1和7。 所以,代码中寻找j的循环应该从n-1到i+1,找到第一个比arr[i]大的元素,然后交换。 例如: for j in range(n-1, i, -1): if arr[j] > arr[i]: break swap(arr[i], arr[j]) reverse(arr, i+1, n-1) 这样就能得到正确的结果。 综上,代码的步骤正确。 现在将这些步骤转化为代码。 在代码中,处理下一个排列的函数: void next_permutation(vector<int> &arr) { int n = arr.size(); int i = n-2; while (i >=0 && arr[i] >= arr[i+1]) { i--; } if (i <0) { reverse(arr.begin(), arr.end()); return; } int j = n-1; while (j >i && arr[j] <= arr[i]) { j--; } swap(arr[i], arr[j]); reverse(arr.begin()+i+1, arr.end()); } 这样的函数可以正确处理下一个排列。 综上,完整的代码结构为: 读取n,读取排列数组。 计算字典序: 预先计算阶乘数组。 维护一个used数组,逐个处理每个元素,统计比当前元素小且未被使用的数目,乘以阶乘。 输出结果。 然后,生成下一个排列,并输出。 需要注意数组的索引是否正确,以及在处理阶乘时是否溢出。 现在,测试代码是否正确。 例如,示例输入: 8 2 6 4 5 8 1 7 3 计算字典序: 按照前面的分析,结果应为8227。 生成下一个排列: 通过函数处理后得到2 6 4 5 8 3 1 7。输出正确。 所以,代码是正确的。</think>### **分步解释** 要解决这个问题,我们需要计算给定排列的字典序值,并找到按字典序的下一个排列。以下是详细的解决方案: --- ### **方法思路** 1. **字典序值计算**: - 对于排列中的每个元素,统计其右侧比它小且未被使用的元素个数。 - 将这些个数分别乘以对应的阶乘(剩余位置的阶乘),累加得到字典序值。 - 例如,排列 `2 6 4 5 8 1 7 3` 的字典序值计算如下: - 第一个元素 `2`,未使用的小于 `2` 的元素有 `1` 个,贡献 `1 * 7!`。 - 第二个元素 `6`,未使用的小于 `6` 的元素有 `4` 个,贡献 `4 * 6!`。 - 以此类推,累加所有贡献得到总和。 2. **下一个排列的生成**: - **步骤1**:从右向左找到第一个比右侧元素小的位置 `i`。 - **步骤2**:在 `i` 右侧找到比 `arr[i]` 大的最小元素 `arr[j]`,并交换它们。 - **步骤3**:将 `i` 右侧的元素反转,使其成为最小升序序列。 - 例如,排列 `2 6 4 5 8 1 7 3` 的下一个排列为 `2 6 4 5 8 3 1 7`。 --- ### **C++代码实现** ```cpp #include <iostream> #include <vector> #include <algorithm> using namespace std; // 计算阶乘数组 vector<long long> get_factorial(int n) { vector<long long> fact(n + 1, 1); for (int i = 1; i <= n; ++i) { fact[i] = fact[i - 1] * i; } return fact; } // 计算字典序值 long long calculate_rank(const vector<int>& perm, int n) { vector<long long> fact = get_factorial(n); vector<bool> used(n + 1, false); // 记录1~n是否被使用 long long rank = 0; for (int i = 0; i < n; ++i) { int x = perm[i]; int count = 0; // 统计比x小且未被使用的元素数量 for (int j = 1; j < x; ++j) { if (!used[j]) { count++; } } rank += count * fact[n - 1 - i]; used[x] = true; } return rank; } // 生成下一个排列 void next_permutation(vector<int>& arr) { int n = arr.size(); int i = n - 2; // 找到第一个i,使得arr[i] < arr[i+1] while (i >= 0 && arr[i] >= arr[i + 1]) { i--; } if (i < 0) { reverse(arr.begin(), arr.end()); return; } // 找到比arr[i]大的最小j int j = n - 1; while (j > i && arr[j] <= arr[i]) { j--; } swap(arr[i], arr[j]); reverse(arr.begin() + i + 1, arr.end()); } int main() { int n; cin >> n; vector<int> perm(n); for (int i = 0; i < n; ++i) { cin >> perm[i]; } // 计算并输出字典序值 cout << calculate_rank(perm, n) << endl; // 生成下一个排列并输出 next_permutation(perm); for (int i = 0; i < n; ++i) { if (i > 0) cout << " "; cout << perm[i]; } cout << endl; return 0; } ``` --- ### **代码解析** 1. **阶乘计算**:预先生成 `fact` 数组存储 `0!` 到 `n!`,避免重复计算。 2. **字典序值计算**: - 遍历排列的每个元素,统计比当前元素小且未被使用的元素个数。 - 累加每个位置的贡献(个数乘以阶乘)。 3. **下一个排列生成**: - 从右向左找到第一个升序位置 `i`。 - 在 `i` 右侧找到比 `arr[i]` 大的最小元素 `arr[j]`,交换后反转右侧序列。 4. **输入输出处理**:读取输入数据,调用函数计算并输出结果。 --- ### **复杂度分析** - **时间复杂度**: - 字典序值计算:$O(n^2)$,需遍历每个元素并统计比它小的元素。 - 下一个排列生成:$O(n)$,单次遍历和反转操作。 - **空间复杂度**:$O(n)$,用于存储阶乘数组和标记数组。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值