5月11日 GRyz 校内竞赛 01分数规划 题解
一道贪心题 \(qwq\)
读完题目,就知道这道题跟字符没什么关系,所以我们可以先把 * 和 ^ 看成 \(1\) 和 \(0\)。
那问题就转化成了怎么着把其中的一个序列翻转一下使其中的 \(1\) 的个数最多和最少。
现在考虑一个问题,怎么求所有分数的可能性:
要知道,最小值与最大值之间的一切值都是可以得到的。
例如现在我们选择翻转一个区间 \([L, R]\),设 \(x\) 是这个区间里 \(1\) 的原始个数,\(y\) 为这个区间内 \(1\) 经过翻转可得到的最大个数。它由于 \(x\) 向 \(y\) 变化,相邻的数字相差 \(1\),所以 \(x\) 和 \(y\) 之间的所有数字必须都存在,所以就是求某区间经过翻转后总的数列中 \(1\) 的最大个数与最小个数的差值。
因为把 \(0\) 翻转成 \(1\) 是一个加分的操作,反之则是减分的操作,所以我们可以把 \(0\) 和 \(1\) 继续转换,分别变为 \(1\) 和 \(-1\),表示翻转该数字后加上或者减去的分数。
所以问题就转化成分别翻转哪个区间使得加分最多和减分最多喽。
接下来就是些板子了……
\(mx\) 记录翻转前边某个位置到当前位置得到的最大的 \(1\) 的个数,\(ans1\) 表示到当前位置最大的答案。
\(mn\) 与 \(ans2\) 反之。
\(x\) 表示当前位置的值
怎么求加分最多:
从头往后遍历,选择一个区间的开头,只要 \(mx + x > 0\) 就说明之前区间开头到当前位置的这个值对后边产生的答案会有贡献,所以之前选择的区间可以继续连续下去,否则的话就说明前边选择的这个区间不会对后边加分,即不会对后边答案的产生做出任何贡献,所以就没必要选择前边那些数了,改变选择区间的开头为后面一个位置……如此重复
那求减分最多你也会了吧……
遍历复杂度 \(O(N)\),复杂度很不错 \(qwq\)
可以结合一下代码:
可以想一下再怎么优化 \(qwq\)
Bong!
/**
* author: zcxxxxx
* creater: 2022.5.9
**/
#include <bits/stdc++.h>
using namespace std;
const int A = 1e2 + 7;
const int B = 1e3 + 7;
const int C = 1e4 + 7;
const int D = 3e5 + 7;
const int E = 1e6 + 7;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + 7;
inline int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') {if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
int main() {
int n = read();
register int ans1 = -INF, ans2 = INF, mx = 0, mn = 0, num = 0;
char ch;getchar();
for(register int x, i = 1; i <= n; ++ i) {
ch = getchar();
x = (ch == '*') ? 1 : 0;
num += x;
x = (!x) ? 1 : -1;
if(mx + x > 0) mx += x;
else mx = 0;
ans1 = max(ans1, mx);
if(mn + x < 0) mn += x;
else mn = 0;
ans2 = min(ans2, mn);
}
printf("%d", ans1 - ans2 + 1);
return 0;
}