题解 CF688A 【Opponents】
好久没写题解了,今天正好有空(主要是在家里待的都要发霉了 ),就写篇水题题解
(第一版)题解部分
言归正传。这道题其实可以看做是一道最大子段和的模板题,对于这道题,我的思路是:先定义一个数组a,用来标记小白是否获胜,,并全部初始化为(为什么?先设个悬念),然后读入时查找每一个字符串里是否有,如果有,就在a数组中相应位置标上,其余保持原状。接着暴力求最小子段和,因为要求连胜场数,所以前面的初始化就派上用场了:
因为如果进行一般的求最大子段和,会被纳入其子段中,而如果把纳入进去,肯定不是最优解,在比较求时会被舍弃,所以不会对结果造成影响。
至于为什么要定义,是因为,考虑最坏情况,若个数组里有个,剩下的也能起到很好的作用来使其被舍弃。
求完之后,直接输出结果就是了。
Code:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e2 + 10; //个人习惯,不喜勿喷
int a[maxn], ans; //定义
int main ()
{
int n, d;
cin >> n >> d;
for(int i = 0;i < d; ++i)
a[i] = -100; //初始化
for(int i = 0;i < d; ++i){
string s;
cin >> s;
for(int j = 0;j < n; ++j)
if(s[j] == '0')
{
a[i] = 1; //在a数组里标记小白是否获胜
break; //降低时间复杂度(虽然也加快不了多少)
}
}
for(int i = 0;i < d; ++i){
int sum = 0;
for(int j = i;j < d; ++j){
sum += a[j];
ans = max(ans,sum);
}
} //暴力求子段和
cout << ans << '\n'; //ans能被直接输出是因为我在a数组里标记的数是1,所以子段和就是连胜场数
return 0;
}
你以为这篇题解结束了?NONONO
(第二版)如何求最大子段和
这里给刚接触动态规划的OIers讲一下如何求最大子段和
1.暴力算法
两层循环,分别枚举头和尾,时间复杂度为
核心代码:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e3 + 10;
int a[maxn], n, ans;
int main ()
{
cin >> n;
for (int i = 0;i < n; i++)
cin >> a[i];
for (int i = 0; i < n; i++) {
int sum = 0;
for (int j = i; j < n; j++) {
sum += a[j];
ans = max(ans, sum);
}
}
cout << ans << '\n';
return 0;
}
但这种算法的时间复杂度太高,用这种方法无法AC全部题目,于是我们便研究出了动态规划的算法。
2.动态规划算法(划重点)
算法思路:
(1):对于全是非正数的序列,只要求序列当中的最大值就行了(用脚趾头想都知道 )
(2):对于一个有正数的序列,那我们要:
<1>:考虑以每一个点结尾的最大子段和,第i个点记作
<2>:显而易见,这个点的前缀和是非负的,因为如果有任何一个前缀是负的,那么就不要那个负前缀了呗,减去那个负前缀肯定更优啊。所以如果,都负了就别选了,这时什么都不选反而更好,最大值应该是,否则,就把选上。
<3>:计算时只要把它从头到尾扫一遍,答案就是所有的最大值。
注:时间复杂度是线性,即算法。
核心代码:
ans = -inf;
for (int i = 1; i <= n; i++) {
cin >> a[i];
ans = max(ans, a[i]);
}
if (ans <= 0) {
cout << ans << endl;
} else {
for (int i = 1; i <= n; i++) {
if (f[i - 1] + a[i] >= 0) {
f[i] = f[i - 1] + a[i];
}
ans = max(ans, f[i]);
}
cout << ans << endl;
}
对于这个代码,我们可以再优化一下空间。
从上面的分析过程中可知,每个,否则,所以代码可优化为:
for (int i = 1; i <= n; i++) {
if (sum >= 0) {
sum += a[i];
} else {
sum = a[i];
}
ans = max(ans, sum);
}

浙公网安备 33010602011771号