C - 代码优化

C++ - Code Optimization

本文关键字:代码优化      更新时间:2023-10-16

我有问题:

,您以字符串的形式给出了一个序列,其中包含" 0"," 1"answers"?"的字符串。假设有k'?’s。然后有2^k的方法可以用'0'或a'1'替换每个'?',给出2^k不同的0-1序列(0-1序列是序列,仅零和一个序列)。

对于每个0-1序列,将其反转的数量定义为以非调整顺序对序列进行排序所需的最小互换数。在此问题中,当所有零出现在所有零之前时,序列都是以非降低顺序排序的。例如,序列11010具有5个反转。我们可以通过以下动作对其进行排序:11010→→11001→→10101→→01101→01011→→00111。

找到2^k序列的反转数量的总和,modulo 10000007(10^9 7)。

例如:

输入:?? 01 - >输出:5

输入:?0? - >输出:3

这是我的代码:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <string.h>
#include <math.h>
using namespace std;

void ProcessSequences(char *input)
{
int c = 0;
/* Count the number of '?' in input sequence
 * 1??0 -> 2
 */
for(int i=0;i<strlen(input);i++)
{
    if(*(input+i) == '?')
    {
        c++;
    }       
}

/* Get all possible combination of '?'
 * 1??0
 * -> ?? 
 * -> 00, 01, 10, 11
 */
int seqLength = pow(2,c);
// Initialize 2D array of integer
int **sequencelist, **allSequences;
sequencelist = new int*[seqLength];
allSequences = new int*[seqLength];
for(int i=0; i<seqLength; i++){
    sequencelist[i] = new int[c];
    allSequences[i] = new int[500000];
}
//end initialize
for(int count = 0; count < seqLength; count++)
{
    int n = 0;
    for(int offset = c-1; offset >= 0; offset--)
    {
        sequencelist[count][n] = ((count & (1 << offset)) >> offset);
        // cout << sequencelist[count][n];
        n++;
    }
    // cout << std::endl;
}   
/* Change '?' in former sequence into all possible bits
 * 1??0 
 * ?? -> 00, 01, 10, 11
 * -> 1000, 1010, 1100, 1110
 */
for(int d = 0; d<seqLength; d++)
{
    int seqCount = 0;
    for(int e = 0; e<strlen(input); e++)
    {
        if(*(input+e) == '1')
        {
            allSequences[d][e] = 1;
        }
        else if(*(input+e) == '0')
        {
            allSequences[d][e] = 0;
        }
        else
        {
            allSequences[d][e] = sequencelist[d][seqCount];
            seqCount++;
        }
    }
}

/* 
 *  Sort each sequences to increasing mode
 * 
 */
// cout<<endl;
int totalNum[seqLength];
for(int i=0; i<seqLength; i++){
    int num = 0;
    for(int j=0; j<strlen(input); j++){
        if(j==strlen(input)-1){
            break;
        }
        if(allSequences[i][j] > allSequences[i][j+1]){
            int temp = allSequences[i][j];
            allSequences[i][j] = allSequences[i][j+1];
            allSequences[i][j+1] = temp;
            num++;
            j = -1;
        }//endif
    }//endfor
    totalNum[i] = num;
}//endfor


/*
 * Sum of all Num of Inversions
 */
int sum = 0;
for(int i=0;i<seqLength;i++){
    sum = sum + totalNum[i];
}

// cout<<"Output: "<<endl;
int out = sum%1000000007;
cout<< out <<endl;

} //end of ProcessSequences method

int main()
{
   // Get Input
   char seq[500000];
   // cout << "Input: "<<endl;
   cin >> seq;
   char *p = &seq[0];
   ProcessSequences(p);
   return 0;
}

结果适用于小尺寸输入,但是对于更大的输入,我获得了时间cpu时间限制&gt;1秒。我也超出了内存大小。如何使其更快,最佳的内存使用?我应该使用什么算法,应该使用哪些更好的数据结构?,谢谢。

动态编程是必需的方式。想象一下,您将最后一个字符添加到所有序列中。

  • 如果是1,则获得XXXXXX1。到目前为止的每个序列显然与每个序列相同。
  • 如果是 0,则需要知道每个序列中已经知道的数量。每个序列的互换数量都会增加。
  • 如果是?,则只需将两个以前的情况添加在一起

您需要计算有多少个序列。对于每个长度和每个数量的(序列中的数量),自然不会大于序列的长度)。您从长度1开始,这是微不足道的,然后继续更长的时间。您可以获得非常大的数字,因此您应该一直计算Modulo 10000007。该程序不在C 中,但应易于重写(数组应初始化为0,INT为32位,长在64位)。

long Mod(long x)
{
    return x % 1000000007;
}
long Calc(string s)
{
    int len = s.Length;
    long[,] nums = new long[len + 1, len + 1];
    long sum = 0;
    nums[0, 0] = 1;
    for (int i = 0; i < len; ++i)
    {
        if(s[i] == '?')
        {
            sum = Mod(sum * 2);
        }
        for (int j = 0; j <= i; ++j)
        {
            if (s[i] == '0' || s[i] == '?')
            {
                nums[i + 1, j] = Mod(nums[i + 1, j] + nums[i, j]);
                sum = Mod(sum + j * nums[i, j]);
            }
            if (s[i] == '1' || s[i] == '?')
            {
                nums[i + 1, j + 1] = nums[i, j];
            }
        }
    }
    return sum;
}

最佳化

上面的代码被编写为尽可能清晰并显示动态编程方法。您实际上不需要数组[len+1, len+1]。您可以从i列计算i+1列,并且永远不要返回,因此两个列足够 - 旧和新。如果您更多地研究它,则发现新列的j仅取决于旧列的jj-1。因此,如果您以正确的方向实现值(并且不覆盖值您需要的值),则可以使用一列。

上面的代码使用64位整数。您真的只需要j * nums[i, j]nums阵列包含小于10000007的数字,而32位整数就足够了。即使2*1000000007也可以适合32位签名的INT,我们可以使用它。

我们可以通过将循环嵌套到循环中的条件而不是条件来优化代码。也许这是更自然的方法,唯一的缺点是重复代码。

%运算符,每个分区都非常昂贵。j * nums[i, j]通常要小得多64位整数的容量,因此我们在每个步骤中都不必进行Modulo。只需观察实际值并在需要时申请即可。Mod(nums[i + 1, j] + nums[i, j])也可以优化,因为nums[i + 1, j] + nums[i, j]始终小于2*1000000007。

,最后是优化的代码。我切换到C ,我意识到intlong的含义有所不同,因此请确保它很清楚:

long CalcOpt(string s)
{
    long len = s.length();
    vector<long> nums(len + 1);
    long long sum = 0;
    nums[0] = 1;
    const long mod = 1000000007;
    for (long i = 0; i < len; ++i)
    {
        if (s[i] == '1')
        {
            for (long j = i + 1; j > 0; --j)
            {
                nums[j] = nums[j - 1];
            }
            nums[0] = 0;
        }
        else if (s[i] == '0')
        {
            for (long j = 1; j <= i; ++j)
            {
                sum += (long long)j * nums[j];
                if (sum > std::numeric_limits<long long>::max() / 2) { sum %= mod; }
            }
        }
        else
        {
            sum *= 2;
            if (sum > std::numeric_limits<long long>::max() / 2) { sum %= mod; }
            for (long j = i + 1; j > 0; --j)
            {
                sum += (long long)j * nums[j];
                if (sum > std::numeric_limits<long long>::max() / 2) { sum %= mod; }
                long add = nums[j] + nums[j - 1];
                if (add >= mod) { add -= mod; }
                nums[j] = add;
            }
        }
    }
    return (long)(sum % mod);
}

简化

时间限制仍然超过?可能有更好的方法可以做到。您可以

  1. 回到开始,找出数学上不同的方法来计算结果
  2. 或使用数学简化实际解决方案

我走了第二条。我们在循环中所做的实际上是两个序列的卷积:

0, 0, 0, 1, 4, 6, 4, 1, 0, 0,... and 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,...
0*0 + 0*1 + 0*2 + 1*3 + 4*4 + 6*5 + 4*6 + 1*7 + 0*8...= 80

第一个序列是对称的,第二个序列是线性的。在这种情况下,可以根据= 16( numSum)的第一个序列的总和来计算卷积之和,而第二个序列的数字与对应于第一个序列中心的第二个序列,即5( numMult)。numSum*numMult = 16*5 = 80。如果我们能够在每个步骤中更新这些数字,我们将用一个乘法替换整个循环,这似乎是这种情况。

如果s [i] =='0',那么numsum不会更改,而nummult不会更改。

如果s [i] =='1',那么numsum不会改变,只有1个位置将整个序列移动一个位置。

如果s [i] =='?'我们将原始和Shiftet序列添加在一起。Numsum乘以2,NumMult增量为0.5。

0.5意味着一点问题,因为它不是整数。但是我们知道,结果将是整数。幸运的是,在这种情况下,在模块化算术中存在两个(= 1/2)的反转。它是H =(mod 1)/2。提醒一下,倒数为2,以至于H*2 = 1 modulo mod。明智地实现将数字乘以2并将numsum划分为2更容易,但是这只是一个细节,我们无论如何都需要0.5。代码:

long CalcOptSimpl(string s)
{
    long len = s.length();
    long long sum = 0;
    const long mod = 1000000007;
    long numSum = (mod + 1) / 2;
    long long numMult = 0;
    for (long i = 0; i < len; ++i)
    {
        if (s[i] == '1')
        {
            numMult += 2;
        }
        else if (s[i] == '0')
        {
            sum += numSum * numMult;
            if (sum > std::numeric_limits<long long>::max() / 4) { sum %= mod; }
        }
        else
        {
            sum = sum * 2 + numSum * numMult;
            if (sum > std::numeric_limits<long long>::max() / 4) { sum %= mod; }
            numSum = (numSum * 2) % mod;
            numMult++;
        }
    }
    return (long)(sum % mod);
}

我很确定有一些简单的方法来获取此代码,但是我仍然看不到它。但是有时候路径是目标: - )

如果序列具有带有索引zero[0], zero[1], ... zero[N - 1]的n ZEROS,则其反转数为(zero[0] + zero[1] + ... + zero[N - 1]) - (N - 1) * N / 2。(您应该能够证明它)

例如,11010具有两个带有索引2和4的零,因此反转的数量为2 + 4 - 1 * 2 / 2 = 5

对于所有2^k序列,您可以分别计算两个部分的总和,然后添加它们。

1)第一部分是zero[0] + zero[1] + ... + zero[N - 1]。给定序列中的每个0贡献index * 2^k,每个?贡献index * 2^(k-1)

2)第二部分是(N - 1) * N / 2。您可以使用动态编程来对此进行计算(也许您应该Google并首先学习)。简而言之,使用f[i][j]使用给定序列的第一个i字符以j零表示序列。