【问题标题】:C++ - Code OptimizationC++ - 代码优化
【发布时间】:2016-12-17 13:16:29
【问题描述】:

我有一个问题:

给你一个序列,以字符串的形式,只有字符“0”、“1”和“?”。假设有 k'?'s。那么有 2^k 种方法将每个 '?' 替换为 '0' 或 '1',得到 2^k 个不同的 0-1 序列(0-1 序列是只有 0 和 1 的序列)。

对于每个 0-1 序列,将其反转次数定义为按非递减顺序对序列进行排序所需的最小相邻交换次数。在这个问题中,当所有 0 都出现在所有 1 之前,该序列以非递减顺序精确排序。例如,序列 11010 有 5 个反转。我们可以按照以下移动排序:11010 →→ 11001 →→ 10101 →→ 01101 →→ 01011 →→ 00111。

求 2^k 序列的求逆次数之和,取模 1000000007 (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 时间限制 > 1 秒。我也超出了内存大小。如何使其更快和最佳内存使用?我应该使用什么算法,我应该使用什么更好的数据结构?,谢谢。

【问题讨论】:

  • 你没有在问题中显示你的代码(所以你的问题很不清楚)。如果你确实展示了你的代码,你的问题将变成一个 fix-my-code 问题,所以这里是题外话。
  • @BasileStarynkevitch 已更新,抱歉,我忘记发布我的代码了。
  • @BasileStarynkevitch 哦,所以有一个修复我的代码问题,抱歉不知道。
  • 这一行“c++;”有理由将其标记为 c++ 代码;)

标签: c++ algorithm performance optimization data-structures


【解决方案1】:

动态编程是要走的路。想象一下,您正在将最后一个字符添加到所有序列中。

  • 如果是1,那么你会得到XXXXXX1。交换次数显然与迄今为止每个序列的交换次数相同。
  • 如果是0,那么您需要知道每个序列中已经存在的数量。交换的数量会随着每个序列的数量而增加。
  • 如果是?你把前面两个case加起来就行了

您需要计算有多少序列。对于每个长度和每个数字(序列中的数字自然不能大于序列的长度)。你从长度 1 开始,这是微不足道的,然后继续更长。你可以得到非常大的数字,所以你应该一直计算模 1000000007。程序不是C++,但应该很容易重写(数组初始化为0,int为32bit,long为64bit)。

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 数组包含小于 1000000007 的数字,32 位整数就足够了。甚至 2*1000000007 可以放入 32bit signed int,我们可以利用它。

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

% 操作符,就像每次除法一样,非常昂贵。 j * nums[i, j] 通常远小于 64 位整数的容量,因此我们不必在每一步中都进行取模。只需观察实际值并在需要时应用。 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) 和对应于第一个序列中心的第二个序列的数字 (numMult) 计算得出。 numSum*numMult = 16*5 = 80。如果我们能够在每一步中更新这些数字,我们将用一个乘法替换整个循环,幸运的是,情况似乎如此。

如果 s[i] == '0' 则 numSum 不变,numMult 不变。

如果 s[i] == '1' 则 numSum 不变,只有 numMult 增加 1,因为我们将整个序列移动了一个位置。

如果 s[i] == '?'我们将原始序列和移位序列相加。 numSum 乘以 2,numMult 增加 0.5。

0.5 表示有点问题,因为它不是整数。但我们知道,结果将是整数。幸运的是,在这种情况下,模运算中存在两个 (=1/2) 作为整数的反转。它是 h = (mod+1)/2。提醒一下,2 的取反是这样一个数字,即 h*2=1 模模。明智地实现,将 numMult 乘以 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);
}

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

【讨论】:

  • 但是它超出了内存限制,我会尝试使用大整数。
  • 代码被编写为(希望)易于理解。你实际上不需要数组 long[len + 1, len + 1],你只需要 long[2, len + 1]。你可以像 nums[(i + 1)%2, j] 一样索引它。
  • 我的索引超出范围。
  • 我添加了优化版本,希望对您有帮助。
  • 你似乎很受限制。我添加了另一个版本。
【解决方案2】:

如果一个序列有 N 个零且索引为 zero[0], zero[1], ... zero[N - 1],则它的反转数将为 (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。你可以使用动态编程来计算这个(也许你应该先用谷歌搜索一下)。简而言之,使用f[i][j] 使用给定序列的第一个i 字符来表示带有j 零的序列号。

【讨论】:

    猜你喜欢
    • 2012-11-13
    • 2013-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-01
    相关资源
    最近更新 更多