B - Count 1's
考えたこと
・答えの候補はたかだか程度なので、全部試せる
・最大値さえわかれば良さそう
・どうやって最大値求めるんだ…?
解法
まず方針として、1の個数を最大にできる場合と、1の個数を最小にできる2つがわかれば良い 。
変化量だけ考えればよいという感じ。をflipしたときに、1の数が増える/ 減る分のだけを考えるという感じ。つまり、flipしたところ・しなかった部分をわざわざ数え上げる必要はない。これも思いつけませんでした。
このは累積和を用いると高速に求めることができる。
まずはじめに、flipするという操作を言い換えると、1→0 ,0→1となるなので、元の数列に対して、1 → -1, 0→ +1 とした数列を考えて、累積和を計算する。
このままだとただの累積和で、を指定することで、その区間をflipしたときの1の数がでわかるが、制約からを全探索はできない。
ここで、のみを全探索して効率的に求める方法を考える。
累積和の配列をとすれば、は0からまでの和となる。
問題は、が最大となる を探し出したい。
これは、はより前に現れているはずなんで、でが最も小さいものを記録しておけば良い。
最小値も同様。
感想
(解説に1 の個数を最小/最大で何個にできるか,は簡単に求まりますとあるが、自分にとっては簡単ではなかった)
難しいと思う。ちなみに公式の紙の解説よりも、ビデオのほうがわかりやすい。
最大値と最小値の間の値はすべて取れるのか、というのも考える必要があるが、これは自分の場合は素直に理解できた。グラフを書くとなめらかになるので、その間の数字は全部取れるだろ、みたいな。
#include <iostream> #include <vector> using namespace std; using Int = long long; int main() { Int N; cin >> N; vector<int>A(N); for(auto& e : A) cin >> e; int maxm = 0, mini = 0; int prevMax = 0, prevMin = 0; int csum = 0; for(auto cur : A) { csum += (cur == 0 ? 1 : -1); maxm = max(maxm,csum - prevMin); mini = min(mini,csum - prevMax); prevMin = min(prevMin, csum); prevMax = max(prevMax, csum); } cout << maxm - mini + 1 << endl; }