毫无意义的胡思乱想
前言
如何从一道简单的题里面想出一些很没有用的东西?
例题
最大连续子段和
给出 n 个数,求他们的最大连续子段和。
暴力解法
暴力解法是
long long ans = 0;
for(int i=1; i<=n; ++i)
{
for(int j=i; j<=n; ++j)
{
long long sum = 0;
for(int k=i; k<=j; ++k)
{
sum += ai[k];
}
chkmax(ans,sum);
}
}
优化途径 1
其中一个优化途径是累积于过往信息。或者,我本人喜欢叫它“站在巨人肩膀上”。时间复杂度
long long ans = 0;
for(int i=1; i<n; ++i)
{
long long sum = 0;
for(int j=i; j<=n; ++j)
{
sum += ai[j];
chkmax(ans,sum);
}
}
然后一个优化方式是抛弃无用计算。或者,我本人喜欢叫它“知道你要干什么”。
这一步优化有点跳,十分有拿着答案找过程的感觉。那么,按照这个思路,我们想想我们到底要干什么。我们要找出这个最大子段和,那我们在 sum 是负数的时候,就应该果断 break 掉。因为接下来的加和肯定不是最大子段和了。不仅要 break,而且还要将循环起点提前,因为这些都肯定不是最大子段和了。
整理一下代码,优化一下实现。时间复杂度
long long ans = 0;
long long sum = 0;
for(int i=1; i<=n; ++i)
{
chkmax(sum,0);
sum += ai[i];
chkmax(ans,sum);
}
优化途径 2
第二个优化途径是通过前缀和快速求最大值。前缀和的思想我们暂时不知道。
for(int i=1; i<=n; ++i)
{
si[i] = si[i-1]+ai[i];
}
long long ans = 0;
for(int i=1; i<=n; ++i)
{
for(int j=i; j<=n; ++j)
{
chkmax(ans,si[j]-si[i-1]);
}
}
然后一个优化方式还是抛弃无用计算。我们要求出最大子段和,就对于同一个 si[i-1] 要求 si[j] 最大。或者,也可以对于同一个 si[j] 要求 si[i-1] 最小。
for(int i=1; i<=n; ++i)
{
si[i] = si[i-1]+ai[i];
}
long long sum = 0;
long long minsi = 0;
for(int i=1; i<=n; ++i)
{
chkmin(minsi,si[i]);
chkmax(ans,si[i]-minsi);
}
再接一个优化,因为这里 si[i] 可以现场算,那么就可以“现场手工打磨”,剥去多余的预处理。
long long si = 0;
long long ans = 0;
long long minsi = 0;
for(int i=1; i<=n; ++i)
{
chkmin(minsi,si);
si += ai[i];
chkmax(ans,si-minsi);
}
这里的 si-minsi 就等价于上面代码中的 sum,两份代码等价。如果我们只关注 si-minsi 的值的话,下面的代码就等价于上面的代码。
总结
代码等价的话,其蕴含的思想也相同。那么,前缀和就是一种“累积的预处理”。也就相当于“预处理你所要累积的”。
本篇文章所讲的都是一点没有用的东西。